Chat Support
Loupe - Log - Monitor - Resolve
Loupe / Developer's Guide / For .NET Core / 6 / 8 / Developer's Guide - Capturing Application Users
Developer's Guide - Capturing Application Users

Loupe works with the built-in .NET concept of the IPrincipal and IIdentity to associate the active user with each log message and other user-specific data.  To work with the wide range of possible .NET Core applications, Loupe first resolves the current IPrincipal when recording data and then uses that to create a rich, detailed ApplicationUser entity. 

Determining the Current User

By default, Loupe checks the static properties for the current thread to attempt to find the current IPrincipal.  If these are null (which is common in .NET Core applications) then Loupe will attempt to use the current process user.

If the ASP.NET Core Diagnostics are enabled then Loupe will capture the current IPrincipal from ASP.NET Core.  This is done by automatically using the ClaimsPrincipalResolver.

You can override the default resolution behavior by implementing an IPrincipalResolver and providing that to Loupe either through the Log.PrincipalResolver property or preferably using the AddPrincipalResolver<T> method of the LoupeBuilder during configuration.

Providing Rich Application User Information

Most authenticated applications will have additional information recorded about each user - like a display name, email address, group memberships, and more.  Loupe is designed to record this information efficiently using an ApplicationUserProvider.

To do this, you need to:

  1. Create an ApplicationUserProvider to translate each IPrincipal (which includes the user name and other data your security system may be storing) into a full user profile.
  2. Specify your ApplicationUserProvider to Loupe using either the AddApplicationUserProvider<T> method of the LoupeBuilder or setting an instance of the provider to Log.ApplicationUserProvider.
This feature is disabled when the Agent is running in Anonymous mode.  In Anonymous mode no user-identifiable information is gathered which would  be defeated by this feature.

Application User Information

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.

How Application Users are Matched

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. 

Custom Properties

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. 

Since the user properties are shared across all applications on the Loupe Server, prefix application-specific values with the display name of the application followed by a period (.) for best display.

Basic Example of Providing User Information

The essential component for translating an IPrincipal into an ApplicationUser is a custom implementation of IApplicationUserProvider.  Here is a simple example of an implementation that translates a custom IPrincipal object into an ApplicationUser:

Multi-User Application User Resolution Example
Copy Code
/// <summary>
/// Translate a custom Principal into an ApplicationUser.
/// </summary>
public class CustomApplicationUserProvider : IApplicationUserProvider
{
    /// <inheritdoc />
    public bool TryGetApplicationUser(IPrincipal principal, Lazy<ApplicationUser> applicationUser)
    {
        //In this example our application has a custom IPrincipal called CustomUserPrincipal.
        //Your application will likely have some other approach you're using for common user info
        if (principal is CustomUserPrincipal ourApplicationPrincipal)
        {
            //Now that we know we have something, populate the Loupe Application User
            var newUser = applicationUser.Value;
            newUser.Caption = ourApplicationPrincipal.FullName;
            newUser.Organization = ourApplicationPrincipal.Customer;
            newUser.EmailAddress = ourApplicationPrincipal.EmailAddress;
            newUser.Phone = ourApplicationPrincipal.PrimaryPhoneNumber;
            return true; //to indicate that we have a valid user.
        }
        return false;
    }
}

Performance Implications of providing Application Users

The Loupe Agent will invoke the ApplicationUserProvider each time it sees a FullyQualifiedUserName that it doesn't yet have details for.  This is done asynchronously from logging so the event will not be raised on the same thread that recorded the log message.  Since it's asynchronous, 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 ApplicationUserProvider will only be invoked by one thread so there will only be one outstanding invocation at a time. 

Best Practices for Providing User Information

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 ApplicationUserProvider is invoked by Loupe asynchronously from recording log messages and metrics 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).  These will be queued for subsequent recording where possible.  Loupe has built-in protection against deadlocks in the 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.

See Also