|
-
Jan 20th, 2011, 02:23 PM
#1
Automating error logging
Hi,
I am trying to create a class library that I can re-use in various projects. The library should handle error logging. It's nothing too special, I just want to log various things such as the exception/message, time of occurrence, some system information such as computer name, username, OS version, etc, and finally the application that the error occured in. I'd then write each entry to an XML file, and later I can create a 'logfile viewer' that reads the xml file and displays the errors in an easy to read fashion.
Anyway, the goal here is that I just have a class LogManager that I have to 'configure' before use in every application I want to use it in. After that I just call some shared methods (WriteError, WriteMessage, etc) and they would be written to the XML file, along with the application / system information.
This configuring should handle various things such as:
- Look up system information (OS, 64-bit or not, username, computername, etc)
- Look up application information (app name, version, etc)
- Look up the 'common app data' folder for that app and create the XML file there if it does not exist yet
All these things should happen automatically. I should not have to pass all the information to the LogManager class, as that would defeat the purpose of having a shared library (it would be less work to just do it separately for every project).
My problem now is as follows... The things I need to look up are pretty easy to find:
- Computername: My.Computer.Name
- Username: My.User.Name
- OS: Environment.OSVersion
- App name: My.Application.ProductName (I think)
- App version: My.Application.Version
- Common app data path: System.Windows.Forms.Application.CommonAppDataPath
However, I cannot use all of these in my separate class library, because they would return information about the class library, and not the project I am trying to log errors for... For example I guess the app name and version would return the name and version of the logging library, and the common app data path would be the folder for the logging library, not the folder for the project I'm logging for.
So, short of supplying these details manually in each separate project (which would defeat the purpose), what are my options here? Can my logging library somehow figure out which application is using it..? I guess not as that would be a circular reference...
So how can I solve this problem?
Thanks!
-
Jan 20th, 2011, 03:21 PM
#2
Re: Automating error logging
Dig into the System.Reflection.Assembly class. The My namespaces and many other members are just shortcuts into that anyway. (That's actually an oversimplification; Application.ProductName goes to great lengths to approximate a name if one doesn't exist, but we'll assume you're filling out your assembly metadata and don't have to worry about it like Microsoft did.)
The key difference between what they do and what your code wants to do comes down to picking which assembly to use. Here's the choices you have for getting an assembly:
- Assembly.GetCallingAssembly() gets the assembly that is calling the current method.
- Assembly.GetEntryAssembly() gets "the process executable in the default application domain".
- Assembly.GetExecutingAssembly() gets the assembly that contains the current line of code.
GetExecutingAssembly() is definitely the wrong one; it will always return the log manager's assembly. GetCallingAssembly() could possibly work, but not if the log manager calls the code that fetches the assembly (documentation also points out optimization can mess this up.) That leaves GetEntryAssembly(). I don't understand what the documentation means with confidence, but it sounds like it suggests it gets the assembly that has the entry point of the program that's running. More often than not that should be your application. Interestingly enough, this is the method that the MS code uses, so you might want to check with Microsoft's code before wasting time writing your own.
If it turns out GetEntryAssembly() isn't reliable, you could have the application call GetExecutingAssembly() and pass the result to your log manager; then the log manager would get the right assembly.
If you do have to write your own, once you get the right assembly it's just a matter of looking for the right information. Surprisingly enough the version, product name, etc. are attributes on the assembly. You may not know this because by default MS hides the file with the attributes; for the most part the philosophy has a patronizing "don't show VB developers something advanced or their head might explode" feel to it. In solution explorer, push the "Show all files" button and look for "AssemblyInfo.vb" under "My Project". Here's some (untested) code that (probably) gets the version:
Code:
Dim assem = Assembly.GetEntryAssembly()
Dim versionType As Type = GetType(AssemblyVersionAttribute)
Dim versionAttribute = CType(assem.GetCustomAttributes(versionType, False).First())
Dim version As String = versionAttribute.Version
Of course, you'll have to make sure your applications provide values for these attributes, but that's a decent practice anyway.
-
Jan 20th, 2011, 03:28 PM
#3
Re: Automating error logging
Thanks, that's definitely helpful.
How about the common app data path? As far as I know that's the best place to store a log file, but I cannot get to it from the Log project since I won't know which application is using it. I could always pass the path along but that's only a last resort, I much prefer it to happen automatically.
EDIT
I think I might be able to get it by looking at how MS implemented it via Reflector. I can see this:
Code:
Public Shared ReadOnly Property CommonAppDataPath As String
Get
Try
If ApplicationDeployment.IsNetworkDeployed Then
Dim data As String = TryCast(AppDomain.CurrentDomain.GetData("DataDirectory"),String)
If (Not data Is Nothing) Then
Return data
End If
End If
Catch exception As Exception
If ClientUtils.IsSecurityOrCriticalException(exception) Then
Throw
End If
End Try
Return Application.GetDataPath(Environment.GetFolderPath(SpecialFolder.CommonApplicationData))
End Get
End Property
This is the System.Windows.Forms.Application.CommonAppDataPath property. Ignoring all but the last line it calls Application.GetDataPath, which seems to just build the path from the assembly information you just told me how to get:
Code:
Private Shared Function GetDataPath(ByVal basePath As String) As String
Dim format As String = "{0}\{1}\{2}\{3}"
Dim companyName As String = Application.CompanyName
Dim productName As String = Application.ProductName
Dim productVersion As String = Application.ProductVersion
Dim path As String = String.Format(CultureInfo.CurrentCulture, format, New Object() { basePath, companyName, productName, productVersion })
SyncLock Application.internalSyncObject
If Not Directory.Exists(path) Then
Directory.CreateDirectory(path)
End If
End SyncLock
Return path
End Function
So that should work
Last edited by NickThissen; Jan 20th, 2011 at 03:32 PM.
-
Jan 20th, 2011, 03:36 PM
#4
Re: Automating error logging
Yeah, the common app data path isn't a standard so much as a convention. You start with a path you can get from Environment.GetSpecialFolder(), then tack company name, product name, and version onto it. Keep in mind that's just a convention. When I'm writing software for myself I tend to omit the company name. I don't know how general you're trying to make this but you might want to leave a way for someone to specify the common app data path if you want to support all applications.
-
Jan 20th, 2011, 03:59 PM
#5
Re: Automating error logging
Just curious (actually, very curious) how are you capturing the errors? Through standard exception handlers? How do you (if you do) capture unhandled exceptions?
"Ok, my response to that is pending a Google search" - Bucky Katt.
"There are two types of people in the world: Those who can extrapolate from incomplete data sets." - Unk.
"Before you can 'think outside the box' you need to understand where the box is."
-
Jan 20th, 2011, 05:04 PM
#6
Re: Automating error logging
The usual way. If I can 'control' the error I just write it as a warning (folder doesn't exist for example), if an exception occurs (try/catch block) somewhere out of my control I write it as an error, and for unhandled exceptions I plan on using the application event that exists for it (though I've never gone that far). It's not so much about handling the errors but just a convenient way to log them. Now I just write a separate logging part for every application that needs it, handling the file IO manually each time, but I figured it would be easier to make a library that can handle all the pre-configuration stuff automatically (such as determining the log file path, creating it, writing the log entries (anything beside the error message), etc).
-
Jan 20th, 2011, 06:54 PM
#7
Re: Automating error logging
 Originally Posted by Sitten Spynne
Dig into the System.Reflection.Assembly class. The My namespaces and many other members are just shortcuts into that anyway. (That's actually an oversimplification; Application.ProductName goes to great lengths to approximate a name if one doesn't exist, but we'll assume you're filling out your assembly metadata and don't have to worry about it like Microsoft did.)
The key difference between what they do and what your code wants to do comes down to picking which assembly to use. Here's the choices you have for getting an assembly:
- Assembly.GetCallingAssembly() gets the assembly that is calling the current method.
- Assembly.GetEntryAssembly() gets "the process executable in the default application domain".
- Assembly.GetExecutingAssembly() gets the assembly that contains the current line of code.
GetExecutingAssembly() is definitely the wrong one; it will always return the log manager's assembly. GetCallingAssembly() could possibly work, but not if the log manager calls the code that fetches the assembly (documentation also points out optimization can mess this up.) That leaves GetEntryAssembly(). I don't understand what the documentation means with confidence, but it sounds like it suggests it gets the assembly that has the entry point of the program that's running. More often than not that should be your application. Interestingly enough, this is the method that the MS code uses, so you might want to check with Microsoft's code before wasting time writing your own.
If it turns out GetEntryAssembly() isn't reliable, you could have the application call GetExecutingAssembly() and pass the result to your log manager; then the log manager would get the right assembly.
If you do have to write your own, once you get the right assembly it's just a matter of looking for the right information. Surprisingly enough the version, product name, etc. are attributes on the assembly. You may not know this because by default MS hides the file with the attributes; for the most part the philosophy has a patronizing "don't show VB developers something advanced or their head might explode" feel to it. In solution explorer, push the "Show all files" button and look for "AssemblyInfo.vb" under "My Project". Here's some (untested) code that (probably) gets the version:
Code:
Dim assem = Assembly.GetEntryAssembly()
Dim versionType As Type = GetType(AssemblyVersionAttribute)
Dim versionAttribute = CType(assem.GetCustomAttributes(versionType, False).First())
Dim version As String = versionAttribute.Version
Of course, you'll have to make sure your applications provide values for these attributes, but that's a decent practice anyway.
Just wanted to get back to this. I think I understand the procedure, and it indeed works fine when I want to get the application title, but it does not work for the version (the example you posted). That's a bit odd... The AssemblyVersion attribute is there in the AssemblyInfo.vb file, just beneath the AssemblyTitle attribute (which it can find)...
I have these functions:
vb.net Code:
Private Shared Function GetApplicationTitle() As String
Dim titleType = GetType(AssemblyTitleAttribute)
Dim attribute = _Assembly.GetCustomAttributes(titleType, False).FirstOrDefault()
If attribute IsNot Nothing Then
Dim titleAttribute = CType(attribute, AssemblyTitleAttribute)
Return titleAttribute.Title
End If
Return String.Empty
End Function
Private Shared Function GetApplicationVersion() As String
Dim versionType = GetType(AssemblyVersionAttribute)
Dim attribute = _Assembly.GetCustomAttributes(versionType, False).FirstOrDefault()
If attribute IsNot Nothing Then
Dim versionAttribute = CType(attribute, AssemblyVersionAttribute)
Return versionAttribute.Version
End If
Return String.Empty
End Function
For the title, it returns the right title. For the version, 'attribute' is Nothing, so it returns empty each time.
The AssemblyInfo.vb file:
vb.net Code:
Imports System
Imports System.Reflection
Imports System.Runtime.InteropServices
' General Information about an assembly is controlled through the following
' set of attributes. Change these attribute values to modify the information
' associated with an assembly.
' Review the values of the assembly attributes
<Assembly: AssemblyTitle("LogManagerTest")>
<Assembly: AssemblyDescription("")>
<Assembly: AssemblyCompany("NickThissen")>
<Assembly: AssemblyProduct("LogManagerTest")>
<Assembly: AssemblyCopyright("Copyright © Microsoft 2011")>
<Assembly: AssemblyTrademark("")>
<Assembly: ComVisible(False)>
'The following GUID is for the ID of the typelib if this project is exposed to COM
<Assembly: Guid("f6cdd100-865f-4258-8926-57604e08898f")>
' Version information for an assembly consists of the following four values:
'
' Major Version
' Minor Version
' Build Number
' Revision
'
' You can specify all the values or you can default the Build and Revision Numbers
' by using the '*' as shown below:
' <Assembly: AssemblyVersion("1.0.*")>
<Assembly: AssemblyVersion("1.0.0.0")>
<Assembly: AssemblyFileVersion("1.0.0.0")>
What am I doing wrong?
Besides this the logging now works fine by the way 
On to the next problem, I guess I will make a thread for that tomorrow (the gist of it: I am going to make a log file reader that displays the errors in a neat way. But what if I decide to change the log file structure along the way? The log file will contain old entries in the old style mixed with new entries in the new style. No way my reader can handle that correctly...)
-
Jan 21st, 2011, 11:28 AM
#8
Re: Automating error logging
The comments in the documentation indicate this attribute is converted into part of the assembly's name and not part of the assembly's metadata. Weird. It does suggest using the GetName() method to get the AssemblyName, which includes a Version property:
Code:
Public Function GetVersion() As Version
Dim assem = Assembly.GetEntryAssembly()
Dim name = assem.GetName()
Return name.Version
End Function
That seems to work.
-
Jan 21st, 2011, 12:10 PM
#9
Re: Automating error logging
I have developed code that for the handles most of your requirements if not all that is housed in a DLL, uses Reflection to get the parent executables information is configured via an XML configuration file and can write data to plain text or XML so that it can be viewed in a DataGridView. My viewer when open goes to the last known exception. Has a custom error dialog with a developer or user interface meaning when a developer see the dialog they see extended info while users see limited information and in either case write to a log file first. Has options to send email messages too.
The only thing holding me back from putting it up is having docs to backup what this is all about, advantages, drawbacks, customization.
Any ways the above was created in VS2003, ported to VS2005, ported and enhanced for VS2008 and simply used in Vs2010 without any changes. Currently used in roughly 10-15 business solutions where the only problems have been developers not configuring it correctly in the XML configuration file.
Primer
http://www.vbforums.com/showthread.php?t=637926
-
Jan 21st, 2011, 01:17 PM
#10
Re: Automating error logging
The attached projects, one a windows form and the other a DLL demonstrates the DLL retrieving information from the parent assembly.
Displays the following to the console from the DLL
Parent name, path, version and build date
Reads an XML sample exception file using the parent assemblies paths via XDocument and LINQ
Hope this assist with your project.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|