dholsop

Aug 13, 2007 at 11:37 AM
Hi,
I was wondering if you could supply or point me to some coding examples for the Logger in the PublicDomain.
General usage and log rolling is of particular interest.
Thanks
Coordinator
Aug 18, 2007 at 4:18 PM
Edited Aug 18, 2007 at 4:18 PM
Hi dholsop, sorry it took me so long to get to this post. I don't get notified of new discussion items and also work has been pretty busy.

I wrote this article to describe the design and usage of PublicDomain.Logging, including the RollingFileLogger that you asked about:

http://www.codeproject.com/useritems/PublicDomain_Logging_API.asp

Let me know if there are any more questions you have after reading that.

Thanks!
Aug 19, 2007 at 2:53 PM
Hi schizoidboy, Thanks for the get back, it got me to first base. I have a few questions: 1.)Is there a standard "vocabulary" for the config strings ? 2) how do I configure the rollover strategy? 3) I noticed that the time format in the log messages was GMT - with 0 offset. How do I get it to be formatted in local time?

Thanks, dholsop

PS Thanks for the timezone work. I've made use of it as our web service renders time in the target zones. Somewhere on the web I noticed that Tokyo was not doing DST this year??
My utility program using the utc offsets was off an hour for Tokyo, but ok elsewhere.
Aug 19, 2007 at 2:54 PM
Edited Aug 19, 2007 at 2:55 PM
oops!
Coordinator
Aug 19, 2007 at 3:29 PM
Edited Aug 19, 2007 at 4:27 PM
Hey dholsop,

For your questions:

1) Is there a standard "vocabulary" for the config strings?

The string is separated by either commas or semicolons, and the values are of the form:

Category=Threshold

The categories are totally abstract -- they are whatever you want them to be. In the article example, each logger's categories are set when the logger is created, for example:

class A {
internal static readonly Logger Log = Program.Loggers.CreateLogger(typeof(A));

However, there's nothing stopping someone from doing:

class A {
internal static readonly Logger Log = Program.Loggers.CreateLogger("blah");

Or even:

namespace nm {
class A {
internal static readonly Logger Log = Program.Loggers.CreateLogger(typeof(A), "blah");

When passing a Type, it will simply use the FullName property of the type. This is generally a good way of specifying what you want to log at a class level.

So, let's take the last case where it's both the Type name and "blah" categories. Then I can turn on this Logger with:

nm.A=Debug

This would be equivalent to:

blah=Debug

The vocabulary on the other side of the equal sign would be all the types of thresholds, so Debug, Warn, Info, Error, Fatal, Infinity. If you leave it off, and you just say:

blah

The threshold will be Debug, meaning that all logging is enabled. Therefore, a common config string where you just want to turn on logging for certain categories would be just a delimited list of categories:

MyNamespace.ClassA;MyNamespace.ClassB;MyNamespace.ClassC

But you can also make something more specific:

MyNamespace.ClassA;MyNamespace.ClassB;MyNamespace.ClassC=Error

2) how do I configure the rollover strategy?

Here would be an example:

Logger logger = new RollingFileLogger("log{0}.log", new FileSizeRollOverStrategy(GlobalConstants.BytesInAMegabyte * 10));

The RollingFileLogger constructor takes an IRollOverStrategy instance. There is a default one called FileSizeRollOverStrategy which rolls over based on the byte-length of the file. GlobalConstants.BytesInAMegabyte * 10 would mean 10Mb, and so it would rollover when the current log file reached 10 Mb.

You can also provide you're own roll over strategy.

3) I noticed that the time format in the log messages was GMT - with 0 offset. How do I get it to be formatted in local time?

That's a good point. It's something I've wanted to do for some time. I will implement it in the next version that I should be putting out pretty soon and post something back here to let you know. I'll probably make the default local time. I think I used UTC by default because it wasn't ambiguous, but I agree that in most cases, people (including myself) don't care about being able to parse logs with absolute data.

> PS Thanks for the timezone work. I've made use of it as our web service renders time in the target zones.
> Somewhere on the web I noticed that Tokyo was not doing DST this year??
> My utility program using the utc offsets was off an hour for Tokyo, but ok elsewhere.

Sure! Glad it helps. So if you use PublicDomain time zone support, Tokyo works? There were a few major bug fixes in 0.2.12.0 and also the latest 0.2.13.0 in case you had some problems, but DST should work fine now in case it didn't work before.

What's cool with the PublicDomain time zone code is that you can actually get the whole olson time zone database in object form and start looking around and browsing (because the raw data is so obfuscated) all the various rules. Some of them are pretty interesting, including things like differences in daylight savings time during war.
Coordinator
Aug 19, 2007 at 5:28 PM
Edited Aug 19, 2007 at 5:29 PM
Just created a new release of PublicDomain 0.2.14.0:

http://www.codeplex.com/publicdomain/Release/ProjectReleases.aspx?ReleaseId=6515

This will print log message timestamps in local time now. I've also added a few loggers such as logging to the Event Log and to an in-memory string.
Aug 19, 2007 at 6:51 PM

Hi schizoidboy. Thanks for the tips on using the logger. It looks like a useful tool...I have use for a "client ID" specific logger with individually controlled logging levels. I can include
the logging level on the client config screen and have the client name appear in the file name for ease of locating it in the directory.

I have a question on how to manage the logger instances. Your examples showed them as "internal static readonly Logger Log ". Can I take the refrence and throw it into a
dictionary keyed on client id so I can pull out the client specific logger as I am running general service software but don't want to turn it on for everyone? If you have a better idea...

About the time format....thanks for the update. That is worth some more thought....when servicing clients in multiple timezones while hosted elsewhere. Maybe a property on each logger to set a signed TimeSpan offset by the caller. I have a routine to figure the offset difference as a TimeSpan which you add to local time to get local time in another zone. That way each file would log relative to the service time zone of the client. Check it out below:
Thanks,
dholsop

public class SPZone
{
// get now in target zone.
public DateTime XlateNowToZone(string target_zone)
{
DateTime now;
now = new DateTime();
now = DateTime.Now;
return now + XlateTimeZone(OrderManagerLib.LocalTimeZone, target_zone);
}

/*
* Return time span to add to current time to get the target
* zone time.
* parameters: home_zone is your local tz
* tartet_zone is the zone you want the time for
*
* Return value is the time span to ADD to your current time.
*
* */

public TimeSpan XlateTimeZone(string homezone, string targetzone)
{
//TzDateTime
TzTimeZone homeZone = PublicDomain.TzTimeZone.GetTimeZone(home_zone);
TzTimeZone targetZone = PublicDomain.TzTimeZone.GetTimeZone(target_zone);

//Console.WriteLine("====> HomeZone:" + homezone + " Remote zone:" + targetzone);

DateTime dt = new DateTime();
dt = DateTime.Now;
TimeSpan Lo_Offset = homeZone.GetUtcOffset(dt); // local gmt offset
TimeSpan Ro_Offset = targetZone.GetUtcOffset(dt); // target gmt offset
TimeSpan To_Offset = new TimeSpan(0); // test for 0 offset
TimeSpan To_add = new TimeSpan(0); // span to add to your current time to get target zone time

//Console.WriteLine("Local Offset:" + LoOffset.ToString() + " Remote offset:" + RoOffset.ToString());

if ((LoOffset < ToOffset) && (RoOffset <= ToOffset))
{// both negative offsets abs(Remote) - Local = time to add to local to get remote time.
Toadd = LoOffset.Duration() + Ro_Offset;
//Console.WriteLine("[[[Both negative Add:" + To_add.ToString());
}
if ((LoOffset > ToOffset) && (RoOffset >= ToOffset))
{// both positive offsets - Remote - Local = time to add to local to get to remote time.
Toadd = RoOffset.Subtract(Lo_Offset);
//Console.WriteLine("[[[Both positive Add:" + To_add.ToString());
}
if (((LoOffset > ToOffset) && (RoOffset < ToOffset)) ||
((LoOffset < ToOffset) && (RoOffset > ToOffset)))
{ // opposite sides of gmt ...check who is which side
Toadd = LoOffset.Duration() + Ro_Offset.Duration();
if (LoOffset < ToOffset)
{ // local is neg :
//Console.WriteLine("[[[Both Opposite, use as is Add:" + To_add.ToString());
}
if (LoOffset > ToOffset)
{ // local is pos
Toadd = Toadd.Negate();
//Console.WriteLine("[[[Both Opposite, flip sign Add:" + To_add.ToString());
}
}
// boundary conditions for local off set at 0 off set...I know, I know...
if ((LoOffset == ToOffset) && (RoOffset == ToOffset)) To_add = new TimeSpan(0);

// the easy case
if ((LoOffset == ToOffset) && (RoOffset != ToOffset)) Toadd = RoOffset;

//Console.WriteLine("[[[[[[ Local ToAdd ====> :" + To_add.ToString() + " HomeZone:" + home_zone);
dt = dt + To_add;
//Console.WriteLine("[[[[[[ Target Time ====> :" + dt.ToString() + " TargetZone:" + target_zone);

return To_add;
}

}



Coordinator
Aug 19, 2007 at 8:42 PM
Edited Aug 19, 2007 at 8:46 PM
> Can I take the refrence and throw it into a
> dictionary keyed on client id so I can pull out the client specific logger as I am
> running general service software but don't want to turn it on for everyone?

Yes, absolutely. Everything is just instances of Logger classes, which you then call .LogX("string") on. If you want to store it in a Dictionary and then use one in your code depending on client ID, that should be no problem:

Dictionary<string, Logger> logs = ...;
logs["clientid"].LogDebug10("log statement");
...

> Maybe a property on each logger to set a signed TimeSpan offset by the caller.

I had pre-empted such a potentiality by adding a TimestampProvider property to the Logger class, which is of type ILogTimestampProvider and returns the current timestamp. The default implementation is LocalLogTimestampProvider which returns DateTime.Now, but you can simply implement your own which returns a time zone sensitive timestamp.

Something like this:

    public class TzSensitiveTimestampProvider : ILogTimestampProvider
    {
        private TzTimeZone m_timeZone;
 
        public TzSensitiveTimestampProvider(TzTimeZone timeZone)
        {
            m_timeZone = timeZone;
        }
 
        public DateTime Now
        {
            get
            {
                return m_timeZone.Now.DateTimeLocal;
            }
        }
    }

And then you would set that for each logger depending on the client:

logs["clientid"].TimestampProvider = new
TzSensitiveTimestampProvider(TzTimeZone.GetTimeZone("..."));
...

One thing I should do is add a +XX:XX string into the log format, so that if you used different TimestampProviders, you would know which time zone each log entry is in. I'll put that into the next release, but if you need it now, you can just provide your own ILogFormatter.

Good luck!
Coordinator
Aug 19, 2007 at 9:33 PM
Hey dholsop,

I just released 0.2.15.0 to address WorkItem #12315, and as part of that, I also made some of the changes to help you in what you were talking about for the time zones in the log messages. I added the TzSensitiveTimestampProvider class, and I also added the ability to get the UtcOffset of the ILogTimestampProvider, so that log messages can have the UTC offset next to the timestamp. This would help you in case you wanted to correlate multiple of your client's logs.

Thanks
Aug 19, 2007 at 9:48 PM
Thanks!! You have been a Big help with logging for our project. This logging utility should do us just fine.

One last question: In multi-threaded situations , are there any precautions I should be aware of to be thread safe while using the Logger?
Thanks,
dholsop
Coordinator
Aug 20, 2007 at 1:31 AM
Sure, no problem.

As far as multi-threading, there shouldn't be any problems, but ultimately that's up to the Logger implementation (e.g. RollingFileLogger). If there are problems, please report them.
Aug 21, 2007 at 10:27 AM
Hi schizoidboy,

I had an issue with the file rollover strategy. I set the file size to 1000 bytes, but it just kept growing. Is there anything I need to do to get the rollover to happen?
Logger logger = new RollingFileLogger(file_name + "{0}.log", new FileSizeRollOverStrategy(/*GlobalConstants.BytesInAMegabyte **/ 1000));

When I install an updated version of PublicDomain, can I just install right over the current package, or do I need to do any prep?

Thanks,
dholsop
Coordinator
Aug 21, 2007 at 3:14 PM
Hi dholsop,

I wrote a little test and it seems to work for me:

RollingFileLogger logger = new RollingFileLogger(FileSystemUtilities.PathCombine(Environment.CurrentDirectory, "log{0}.log"), new FileSizeRollOverStrategy(/*GlobalConstants.BytesInAMegabyte **/ 1000));
Console.WriteLine(logger.FileName);
logger.Threshold = LoggerSeverity.None0;
for (int i = 0; i < 100; i++)
{
logger.LogDebug10("test{0}", i);
}
Console.Write("Done: ");
Console.ReadKey(true);

And then after running it:

Directory of C:\temp\play\play\bin\Debug

08/21/2007 11:09 AM <DIR> .
08/21/2007 11:09 AM <DIR> ..
08/21/2007 11:09 AM 1,004 log1.log
08/21/2007 11:09 AM 1,014 log2.log
08/21/2007 11:09 AM 1,014 log3.log
08/21/2007 11:09 AM 858 log4.log

It's not exactly 1000 bytes because the logger won't actually chop it off to make it exact.

Do you have a code sample I could see? Do you get any kind of logging at all, or is it logging but not rolling over?

There is nothing special you need to do when installing a new version of PublicDomain. The lastest one you install should always take precedence, but you can always check what is in the GAC. If you go to Start -> Program -> Microsoft Visual Studio 2005 -> Visual Studio 2005 -> Visual Studio 2005 Command Prompt, and then run:

gacutil /l PublicDomain

It should list the version that is in the GAC.

Thanks
Aug 22, 2007 at 11:07 AM
Edited Aug 24, 2007 at 12:11 AM
Hi schizoidboy,
That helped some...I think it was not getting the path to the log file for rolling. I am having a problem with the
file name and subsequent rollovers. I UPGRADED version 0.2.15.0

The first file name generated is <basefilename>1.log

First rollover generates <basefilename>2005.log

Second rollover generates <basefilename>2006.log which continues to grow and never rolls over.

This is the code I am using. I am calling the LogManager.log() method from a button press on a windows form.
Thanks,
dholsop

public class LogManager
{
private Dictionary<int, Logger> LogerList = new Dictionary<int, Logger>();
String fileName = "LogFile"; // ** not used right now ... see dictionary below //default log file base name
String Category = "StaffSpan"; // default category
LoggerSeverity defaultThreshold = LoggerSeverity.Debug10; // default to verbose
private Dictionary<int, string> LogFileNames = new Dictionary<int, string>(); // test file name by facility id

// constructor
public LogManager(int severity_threshold, String filename, String category)
{
defaultThreshold = (LoggerSeverity)severity_threshold; // set default threshold for loggers.
fileName = filename;
Category = category;
LogFileNames.Add(101, "FirstLogFileName"); // test file name for facility 101
LogFileNames.Add(201, "SecondLogFileName");// test file name for facility 201
}
// constructor
public LogManager(String filename, String category)
{
fileName = filename;
Category = category;
}
// constructor
public LogManager(){ } // use defaults

void CreateLogger(int facilityid, string filename, string category)
{
// look up facility name, tz, logging priority etc from DB
//Logger logger = new RollingFileLogger(file_name + "{0}.log", new FileSizeRollOverStrategy(/*GlobalConstants.BytesInAMegabyte **/ 1000));
Logger logger = new RollingFileLogger(FileSystemUtilities.PathCombine(Environment.CurrentDirectory, file_name + "AA{0}.log"), new FileSizeRollOverStrategy(/*GlobalConstants.BytesInAMegabyte **/ 3000));
logger.Threshold = defaultThreshold;
logger.Category = category;
logger.Formatter = new SimpleLogFormatter();
LogerList.Add(facility_id, logger); // keyed by facility id
}

public void SetThreshold(int facility_id, int ls)
{
this.LogerListfacility_id.Threshold = (LoggerSeverity)ls;
}


// try logging, create logger if needed
public void log(int facility_id, int severity, object entry, params object[] formatParams)
{
try
{
this.LogerListfacility_id.Log((LoggerSeverity)severity, entry, formatParams);
}
catch (Exception ex)
{
try
{
CreateLogger(facility_id, LogFileNamesfacility_id, Category);
this.LogerListfacility_id.Log((LoggerSeverity)severity, entry, formatParams);
}
catch (Exception exc)
{
Console.WriteLine("Could not create logger!! facility="+facility_id.ToString() + " filename=" + fileName + " Category="+Category + " Exception="+exc.ToString());
}
}
}

}

Aug 22, 2007 at 11:12 AM
Edited Aug 24, 2007 at 1:30 AM
It looks like the very first log message is landing in the first file. The subsequent log entries land in the next file, which rolls over but the next file does not roll over !?
Coordinator
Sep 8, 2007 at 3:53 AM
Edited Sep 8, 2007 at 3:53 AM
Hi dholsop,

Sorry it took me so long to get to this.

So, I tried your code, and it worked fine. Here is my test program:

LogManager manager = new LogManager((int)LoggerSeverity.Debug10, "test", "test");
for (int i = 0; i < 1000; i++)
{
    manager.log(i % 2 == 0 ? 101 : 201, (int)LoggerSeverity.Debug10, "test{0}", i);
}

And here is my directory listing after running the program. I don't get a rollover with "2005" in the name:

 Directory of C:\temp\play\play\bin\Debug
 
09/07/2007  11:49 PM    <DIR>          .
09/07/2007  11:49 PM    <DIR>          ..
09/07/2007  11:49 PM            16,384 play.exe
09/07/2007  11:49 PM            15,872 play.pdb
09/23/2005  07:56 AM             5,632 play.vshost.exe
09/07/2007  11:47 PM         1,343,488 PublicDomain.dll
09/07/2007  11:47 PM         1,281,536 PublicDomain.pdb
09/07/2007  11:47 PM           783,041 PublicDomain.xml
09/07/2007  11:48 PM             3,014 testAA1.log
09/07/2007  11:48 PM             3,001 testAA2.log
09/07/2007  11:48 PM             3,038 testAA3.log
09/07/2007  11:48 PM             3,038 testAA4.log
09/07/2007  11:48 PM             3,038 testAA5.log
09/07/2007  11:48 PM             3,038 testAA6.log
09/07/2007  11:48 PM             3,038 testAA7.log
09/07/2007  11:48 PM             3,038 testAA8.log
09/07/2007  11:48 PM             3,038 testAA9.log
09/07/2007  11:48 PM             3,038 testAA10.log
09/07/2007  11:48 PM             3,038 testAA11.log
09/07/2007  11:48 PM             3,038 testAA12.log
09/07/2007  11:48 PM             3,038 testAA13.log
09/07/2007  11:48 PM             3,038 testAA14.log
09/07/2007  11:48 PM             3,038 testAA15.log
09/07/2007  11:48 PM             3,038 testAA16.log
09/07/2007  11:49 PM             3,021 testAA17.log
09/07/2007  11:49 PM             3,043 testAA18.log
09/07/2007  11:49 PM             3,038 testAA19.log
09/07/2007  11:49 PM             3,038 testAA20.log
09/07/2007  11:49 PM             3,038 testAA21.log
09/07/2007  11:49 PM             3,038 testAA22.log
09/07/2007  11:49 PM             3,038 testAA23.log
09/07/2007  11:49 PM             3,038 testAA24.log
09/07/2007  11:49 PM             3,038 testAA25.log
09/07/2007  11:49 PM             3,038 testAA26.log
09/07/2007  11:49 PM             3,038 testAA27.log
09/07/2007  11:49 PM             3,038 testAA28.log
09/07/2007  11:49 PM             3,038 testAA29.log
09/07/2007  11:49 PM             3,038 testAA30.log
09/07/2007  11:49 PM             3,038 testAA31.log
09/07/2007  11:49 PM             2,891 testAA32.log

Do you have any numbers in your path? Because you might be getting hit by this bug which will be fixed in 0.2.16.0: http://www.codeplex.com/publicdomain/WorkItem/View.aspx?WorkItemId=12363
Sep 8, 2007 at 4:12 PM
Edited Sep 8, 2007 at 6:17 PM
Hi schizoidboy,
I did have numbers in the name, the facility number. That may have been the problem. I was using 0.2.15.0
I'll download the newer version.
Thanks for the getback.

Hey....I don't see 0.2.16.0 on the site for dwnld.


One situation I am using the logger is in a WebService. There
is no persistant process, the file is opened, closed and reopened. It appears to truncate the file on reopen. Is there a way to have it append on reopen? Thanks,

dholsop
Coordinator
Sep 8, 2007 at 6:58 PM
Hi dholsop,

0.2.16.0 should be available either today or tomorrow hopefully.

Also, you are right that on every write it opens and closes the file stream; however, it always appends. Here is how the stream is created:

new FileStream(fileName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite)

So, you should not be seeing any kind of truncation. It might possibly be some kind of bug or race condition. If you could create a reproduceable test case, that would be great...

Thanks!
Kevin
Sep 8, 2007 at 11:41 PM
Appreciate the update and the info,
I may have to come up with a log server which collects log packets, acting like a funnel and then log the activity.
This is my first venture into WebService land...
Thanks,
Dan
Coordinator
Sep 8, 2007 at 11:53 PM
Hi Dan, I just uploaded release 0.2.16.0 (http://www.codeplex.com/publicdomain/Release/ProjectReleases.aspx?ReleaseId=6962).

Is your WebService simply an ASP.NET .asmx service with a WebService attribute? If so, then the logging should be no problem. If you have more background info, I might be able to help...

Thanks,
Kevin
Sep 9, 2007 at 11:46 PM
Hi Kevin,
I just downloaded the new version. Thanks!

The web service is an access to a business-logic dll, coded in C# with access to SQL Server DB.
The dll is also accessed by a windows service process. The windows server process creates a long standing
logger.

The web service instanciates the dll, runs its command and exits. This also generates a lot of overhead as I am
using the TimeZone package. Is there a better way to do this? In the UNIX world there are shared objects which
stay in memory and don't need to be initialized repeatedly, sort of like the old TSRs.

I am using the logger in the web method exposures as well as in the dll.

Thanks,
Dan
Coordinator
Sep 10, 2007 at 12:37 AM
Hi Dan,

Well, first, the TimeZone package is not loaded if you're just using the Logger package, unless you're using the TzSensitiveTimestampProvider. I'm looking into alternatives rather than statically loading all time zones in the static initializer.

I've done some limited testing and the initialization time is about 1-2 seconds, and will hold around 700Kb of objects in memory for the lifetime of the program. Now, this initialization only occurs if you use the TZ package, and only once per AppDomain. Memory wise, this isn't a problem since the objects get stuffed into the second generation of the heap and don't impact GC. CPU wise, this only has an impact for non-server related applications. For example, if I understand your example, it almost seems like you initialize the DLL every single web service call. In this case, the TZ load time will have an aggregate performance impact. In web servers (e.g. ASP.NET apps or Windows Services) this is not a problem since the AppDomain recycles infrequently or never in the case of Windows Services.

If I understand your example correctly, you have a Windows Service process which has the Web Service server? How does the web service instantiate the DLL? It seems like that will be your big problem -- you should either host the DLL in an AppDomain which you load into your Web Service or cache the Assembly that you load.

Yes, instantiating DLLs/AppDomains/Assemblies is all incredibly expensive and you should cache wherever possible. I think .NET JITs across the machine, so it's not quite shared objects, but I think that part is not duplicated.

How are you hosting the web service and how are you invoked in the business-logic dll?
Sep 10, 2007 at 2:14 PM
Edited Sep 10, 2007 at 4:03 PM
Hi Kevin,

I created a web service project with VS, and in the constructor, I instanciate the business logic dll. This gives the web service methods access to the business methods in the dll. I am hosting the service(right now) on the same machine under IIS with virtual directory. I created a web app, a web reference with VS and created the web service proxy dll using wsdl and csc, placing it in the bin directory of the web app. The web app can now call web service methods which in turn have access to the business logic dll.

Is there a way to cache this business logic dll instance on the web service?

I also have a windows service which creates an instance of this dll and runs in the background with no user interaction. This is a long running process.

Thanks,
Dan
Coordinator
Sep 10, 2007 at 4:03 PM
Hi Dan,

When you say you created a "web service" project, do you mean the "ASP.NET Web Service Application" project?

I'm a bit confused how you're instantiating the business logic. Could you provide a code snippet? If you reference the business logic DLL and simply instantiate instances of classes from that DLL, then there is no problem, because the DLL will be loaded into the ASP.NET AppDomain, so all of the static initialization will only occur once.

If I understand correctly, then both the windows service and the web service are essentially long running, and so yes, you should cache any DLL initialization. Once I understand how you're doing that initialization, then we should be able to figure out a way to cache.

Are there specific performance problems you're running into? I highly suggest running the CLR Profiler 2.0 made by Microsoft which will narrow down any issues. You should also use built-in performance counters (Start -> Run -> perfmon).

Profiler:
http://www.microsoft.com/downloads/details.aspx?FamilyId=A362781C-3870-43BE-8926-862B40AA0CD0&displaylang=en

Kevin
Sep 10, 2007 at 4:19 PM
Edited Sep 10, 2007 at 6:24 PM
Kevin,
Yes, ASP.NET Web Service (under File->New->Web Site-> ASP.NET web service )
Here is the code snippit from the web service. There are 2 DLLs, the Logger and OrderManager.

When you say that the WebService is long-running, I don't mean to say that the method calls should take long to execute. Just that the service is available long term.

I'll check into the tools you mentioned... CLR Profiler 2.0

Thanks,
Dan


using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Configuration;
using System.Configuration;
using System.Text;
using StaffSpanFileLogger;
using OrderManagerLibrary;



WebService(Namespace = "StaffSpanWS")
WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)
public class Service : System.Web.Services.WebService
{
static OrderManagerLib oml = null; // BUISNESS DLL WILL GO HERE
string UserName;
string DBName;
string HostName;
string Password;
LogManager lm = null; // LOGGER DLL WILL GO HERE

public Service () {
try
{
//Uncomment the following line if using designed components
//InitializeComponent();
// read web
UserName = ConfigurationManager.AppSettings"UserName";
DBName = ConfigurationManager.AppSettings"DBName";
HostName = ConfigurationManager.AppSettings"HostName";
Password = ConfigurationManager.AppSettings"Password";
lm = new LogManager(10, "StaffSpanWS");
lm.log(100, 10, "HostName={0} DBName={1} UserName={2} Password={3}", HostName, DBName, UserName, Password);


oml = new OrderManagerLib("dbsvr", "mydb", "user", "pswd");
}
catch (Exception ex)
{
lm.logException(100, ex, 10);
}
}



WebMethod
public OMOrder WSOMCreateOrders(OMOrder omOrder)
{
oml.OMCreateOrders(om_Order);
om_Order.Msg = "This is an Order Creation Reply";
return om_Order;
}

.... and others....
Coordinator
Sep 10, 2007 at 8:57 PM
Hi Dan,

Yes, I mean to say it is "long running" from the perspective of AppDomains. The web server is long running "hosting" the web service, and this is good in your case, because caching will go a long way to help for each request. Of course, each request itself should be very short.

Looking at your code, the DLL does not get loaded on each request. The DLL will get loaded once into the AppDomain, therefore you should have no problems with duplicated TZ memory information, nor load times.

I believe there is a way to specific whether or not a new instance of your "Service" class is instantiated on each request (i.e. a Singleton). You already have a log item in there in the constructor, so you should be able to answer that. If you are seeing the constructor getting called more than once, I suggest investigating the Singleton option (not sure where that is), or more easily just convert your constructor into a static initializer, e.g.:

from:

 public Service () {
try
{
...

to:

 static Service () {
try
{
...

The only thing that changes is instead of "public Service" to declare the instance constructor, "static Service" is essentially the "static constructor."

What are the actual problem symptoms that you're seeing?

Kevin
Sep 11, 2007 at 12:13 AM
Thanks Kevin,

I'll try the static approach....
Right now we are coming up on some enhancements to the sw. I'll have to get on that for now and revisit this, most likely next
week. I'll do some more diags then.

One question on the logging format: Should I have to add my own " \n\r" at the end of the log entries. My log entry lines get run
together if I don't add this at the end of the log string.

Thanks,
Dan
Coordinator
Sep 11, 2007 at 2:14 AM
Edited Sep 11, 2007 at 2:14 AM
Hey Dan,

I'd highly suggest CLR profiler and perfmon, they will tell you exactly where you need to optimize. They're a bit hard to use, but if you need any help, let me know.

You know, I totally forgot to put the \r in, so only a \n is written; however, depending on the editor, that works for most systems. So your log lines should not run together.

I probably won't be putting out a new release for a bit of time for this small of change, so if you want, you can just copy C:\Program Files\Public Domain\Logging\FileLogger.cs into your project, and rename it to CustomFileLogger or something, and then on line 66, you can add whatever characters you want to output for each line. For the ultimate solution, I'll probably use the constant Environment.Newline which I think is \r\n.

Good luck,
Kevin