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

    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