By default, Loupe captures the user name of the Current Principal associated with each thread as it writes log entries. This provides at best a simple user name, typically in the form of <Domain>\<User> or <User Name>. This is a good start, but there is only so far Loupe can do with this limited amount of information.
To maximize the benefit you get from Loupe's Application User tracking you'll want to provide additional information for each unique user of your application. To do this, you need to:
When responding to the ResolveApplicationUser Event you can provide additional details about the user including:
Field Name | Description | Default Value |
---|---|---|
Key | Optional. The authoritative key provided by the Agent for this user. | None |
FullyQualifiedUserName | Read Only. The fully qualified user name as originally provided (E.g. DOMAIN\User or user@domain) | The user name from the IPrincipal |
Caption | Optional. A display caption for the user - often their full name. | None. The server will attempt to fill by parsing FullyQualifiedUserName |
EmailAddress | Optional. The email address used to communicate with the user. | None. The server will use FullyQualifiedUserName if it appears to be an email address |
Phone | Optional. The phone number or other telecommunication alias for the user. | None |
Organization | Optional. The organization this user belongs to - such as a customer. | None. The server will attempt to fill by parsing FullyQualifiedUserName |
Title | Optional. A title for the user (e.g. job title) | None |
TimeZoneCode | Optional. The time zone the user is associated with | None |
Tenant | Optional. The specific tenant for the user account in a multitenant system. Application-specific. | None |
Role | Optional. The primary role of the user (such as Administrator or User or Guest). Application-specific. | None |
Properties | Optional. A collection of name/value pairs to be recorded with the user. | An empty Dictionary |
This information will be sent along with each log message and other user-specific data to the Server where it will be merged with other information available about the same user to provide the most complete picture possible for your support staff.
Application User information provided in this event will overwrite server-derived values for Caption, EmailAddress, and Organization. The most current information for all non-key fields will update information in the server, allowing additional information to be stored over time and automatically updated as the user is changed in your application.
To match up user information between multiple applications the FullyQualifiedUserName is assumed to be globally unique. This means that all users with the name "MYCOMPANY\AUser" will be considered the same person, similarly auser@mycompany.com.
If this is inadequate for your purposes (for example if every installation has the same basic users and there is no domain to qualify the information) you can override this match by providing a Key. If a Key is specified then only an exact match by Key will be considered the same user - even another user with the same FullyQualifiedUserName but no Key will not be merged with the entry with the Key. The Key field is never displayed to the user so it can be something that's hard to read like a GUID. Good examples would be a database GUID primary key (but not an identity key since that will duplicate between databases) and a Windows SID.
You can record custom name/value pairs for each user to add information that doesn't fit well into one of the built-in fields. These properties will be merged with existing properties on the server based on a case-insensitive name comparison.
Names can be dot-delimited (.) and may contain spaces.
Setting a value to null will remove the property from the user when merged with the server record.
Multi-User Application User Resolution Example |
Copy Code
|
---|---|
private void OnResolveApplicationUser(object sender, ApplicationUserResolutionEventArgs e) { //In this example our application has a custom IUserPrincipal called CustomUserPrincipal. //Your application will likely have some other approach you're using for common user info var ourApplicationPrincipal = e.Principal as CustomUserPrincipal; if (ourApplicationPrincipal == null) //it must be a regular windows user from a background thread or //some other scenario where we aren't getting the true app user return; //Now that we know we have something, populate the Loupe Application User var newUser = e.GetUser(); newUser.Caption = ourApplicationPrincipal.FullName; newUser.Organization = ourApplicationPrincipal.Customer; newUser.EmailAddress = ourApplicationPrincipal.EmailAddress; newUser.Phone = ourApplicationPrincipal.PrimaryPhoneNumber; } |
The Loupe Agent will raise the ResolveApplicationUser event each time it sees a FullyQualifiedUserName that it doesn't yet have details for, provided there are any subscribers to this event. This is done asynchronously from logging so the event will not be raised on the same thread that recorded the log message. This means that it normally will have no effect on the responsiveness of the application unless there is a dramatic slowdown in providing user information compared to the rate of logging. That said, it's recommended to make this method as fast as feasible, particularly if there is any synchronous logging in your application.
While Loupe is multithreaded, the ResolveApplicationUser event will only be raised by one thread so there will only be one outstanding invocation at a time.
It's recommended that your application have already loaded all of the information you want to record onto the object you have that implements IPrincipal. These interfaces are designed to allow you to provide user profile information via claims or your own custom implementation. If you follow this approach, when the event is raised you can cast your IPrincipal object to the correct type and interrogate for all of the relevant information without having to do any additional database calls.
Since the ResolveApplicationUser event is asynchronous relative to recording data with the Loupe Agent it's possible to log messages and metrics while attempting to resolve the user (such as might happen if you call a database to retrieve information and are recording database activity to Loupe) and there are protections to avoid deadlocking in the extreme case where these log messages would have to be recorded synchronously. Despite this, it's recommended for performance reasons to minimize the work done during this event.
Gather information necessary to populate the Application User before calling the GetUser Method to start setting the information. Once this method has been invoked, the resulting application user will be recorded and locked in that configuration for the duration of the session. Therefore, if you may not be able to contact a data store to retrieve the user information you're better off deferring specifying the user information until it is all available.