Quick Tip: Adding a Saved Search to LogViewer in Umbraco with code

Heya👋
I wanted to share a quick tip with you all, that I think more package developers in the Umbraco community should consider doing, by adding a Saved Search to the Log Viewer built inside Umbraco. Allowing users of your package to easily filter or find logs related to your package.

So let’s jump into the code and see how it easy it is to do along with some suggestions of saved searches you could add to your package by using a package migration.

Adding a Package Migration Plan

This code snippet adds a package migration plan, which is used to determine if any steps of a migration need to be performed for a package. Over time with upgrades and new versions of your package you may add one or more migrations to add database tables or run other pieces of code such as adding saved searches to the Log Viewer.

For more information on Package Migrations take a look at the documentation from Umbraco.

using QuickTipSavedLogSearch.MyPackage.Migrations;
using Umbraco.Cms.Core.Packaging;

namespace QuickTipSavedLogSearch.MyPackage
{
    public class MyPackageMigrationPlan : PackageMigrationPlan
    {
        public MyPackageMigrationPlan() : base("My Package")
        {
        }

        protected override void DefinePlan()
        {
            // Ensure you use a unique guid for your migration plan steps
            To<AddSavedSearchToLogViewer>(new Guid("7E17C412-6061-470C-A8C8-1E2A23EF3096"));
        }
    }
}

Adding Saved Searches

The key thing from the code snippet below is that we inject ILogViewerConfig into the constructor of the PackageMigration code, so that we can use the methods such as GetSavedSearches() and AddSavedSearch(). With this we can save useful and specific Log Queries to help users of our package find logs only specific to our package code.

using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.IO;
using Umbraco.Cms.Core.Logging.Viewer;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Strings;
using Umbraco.Cms.Infrastructure.Migrations;
using Umbraco.Cms.Infrastructure.Packaging;

namespace QuickTipSavedLogSearch.MyPackage.Migrations
{
    public class AddSavedSearchToLogViewer : PackageMigrationBase
    {
        private ILogViewerConfig _logViewerConfig;
        private ILogger<AddSavedSearchToLogViewer> _logger;

        public AddSavedSearchToLogViewer(
            IPackagingService packagingService, 
            IMediaService mediaService, 
            MediaFileManager mediaFileManager, 
            MediaUrlGeneratorCollection mediaUrlGenerators, 
            IShortStringHelper shortStringHelper, 
            IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, 
            IMigrationContext context, 
            IOptions<PackageMigrationSettings> packageMigrationsSettings,
            ILogViewerConfig logViewerConfig,
            ILogger<AddSavedSearchToLogViewer> logger) 
            : base(packagingService, mediaService, mediaFileManager, mediaUrlGenerators, shortStringHelper, contentTypeBaseServiceProvider, context, packageMigrationsSettings)
        {
            _logViewerConfig = logViewerConfig;
            _logger = logger;
        }

        // Find all logs that are from our package that starts with our namespace 'QuickTipSavedLogSearch.MyPackage'
        const string AllLogsQuery = "StartsWith(SourceContext, 'QuickTipSavedLogSearch.MyPackage')";

        // Find all logs that are from our package that starts with our namespace 'QuickTipSavedLogSearch.MyPackage'
        // AND the log level is either Error or Fatal OR the log has an exception property
        const string ErrorLogsQuery = "StartsWith(SourceContext, 'QuickTipSavedLogSearch.MyPackage') and (@Level='Error' or @Level='Fatal' or Has(@Exception))";

        protected override void Migrate()
        {
            // Test logline to help show it in the saved search filter
            _logger.LogInformation("Adding saved searches to log viewer");

            // Check to see if the existing saved searches for the log viewer contains our searches we want to add
            var allSavedSearches = _logViewerConfig.GetSavedSearches();
            
            // If the saved searches already contains our searches, then we don't need to add them (use singleordefault null check to check)
            // Unlikely but a user may have added these searches themselves and Umbraco API for this doesn't handle it for us if we add a duplicate
            if (allSavedSearches.SingleOrDefault(x => x.Query.Equals(AllLogsQuery, StringComparison.OrdinalIgnoreCase)) != null && allSavedSearches.SingleOrDefault(x => x.Query.Equals(ErrorLogsQuery, StringComparison.OrdinalIgnoreCase)) != null)
            {
                _logger.LogInformation("Saved searches already exist, skipping adding them");
                return;
            }

            // Add the saved searches
            _logViewerConfig.AddSavedSearch("[My Awesome Package] Find all Logs", AllLogsQuery);
            _logViewerConfig.AddSavedSearch("[My Awesome Package] Find all errors", ErrorLogsQuery);

            // Test error/exception to help show it in the saved search filter
            _logger.LogError(new Exception("Some exception"), "Some error with a counter {Counter}", 40);

        }
    }
}

Log Queries

Log queries is a way to search and filter with expressions to find the logs we are interesting in reading more about. We add two queries with the example code above and we can take a brief look at what they are doing.

StartsWith(SourceContext, 'QuickTipSavedLogSearch.MyPackage')

This query finds all logs where the property SourceContext starts with the value QuickTipSavedLogSearch.MyPackage. The property SourceContext is the full namespace of where the log was generated from, so we can use this to easily help find logs that are coming from our own code in the namespace of our code.

StartsWith(SourceContext, 'QuickTipSavedLogSearch.MyPackage') and (@Level='Error' or @Level='Fatal' or Has(@Exception))

This query builds upon from the one above and finds all logs where the property SourceContext starts with the value QuickTipSavedLogSearch.MyPackage AND the Log Level is Error or Fatal or the Log has an @Exception property.

Its really quite simple and can make a big difference to the quality of life for developers and users using your package, to help and find the information directly tied to your package and remove the noise of other log entries.

I hope this helps inspire you to give a little DX love and to start experimenting with creating useful Log Queries for the Log Viewer inside Umbraco.

Quick Tip: How to customise your Umbraco 13+ login screen

With the new Umbraco V13 due out soon-ish, December 14th according to the LTS release plan there is a snazzy new back-office login screen that has been built that you can see below.

A screenshot of the new Umbraco V13 modern login screen
A fresh modern new login screen to the Umbraco CMS application

This new login screen for the Umbraco backoffice has been built using WebComponents and the Umbraco UI library, the same technologies which is planned for the rewrite of the entire Umbraco backoffice for V14.

What can I customise?

So you may be wondering what can you customise of this new Umbraco backoffice login screen. We can replace the main image of a lady typing on a laptop in her bed, along with the logo that overlays the image.

You can see from the screenshot below what we can customise with the pink outlined boxes.

The pink outlined boxes show what we can easily customise on the new Umbraco login screen

How do I customise it?

If you need to change or customise the login screen for your client, so the CMS is more branded to their corporate look and feel then you can still do so, like you have done in previous versions of Umbraco.

This is done with configuration settings and can be set with appsettings.json, environment variables or any configuration method you prefer for your .NET applications. But for this example we will use the appsettings.json file that Umbraco ships with.

I have set the three properties under the Umbraco:CMS:Content configuration section with paths to the main image and the logo I want to use. I have placed the files I wanted to use for this customisation inside wwwroot/img so the files can be served from the application.

"Umbraco": {
 "CMS": {
  "Content": {
   "LoginBackgroundImage": "/img/my-brand-photo.jpg",
   "LoginLogoImage": "/img/my-logo.svg",
   "LoginLogoImageAlternative": "/img/my-logo-small.svg"
  },
 }
}
A customised V13+ Umbraco login screen for Coding Unicorns

That’s it, it really was a quick tip on customising the new Umbraco login screen, be it if you prefer to change it to a photo of the Umbraco community, a different stock photo or something to match the brand, you now know how to do it 🥰

Update

After speaking with Jacob the lead of Umbraco HQ Frontend Development team, we chatted and disucss a couple of pain points and how this could be improved.

Code Changes PR
https://github.com/umbraco/Umbraco-CMS/pull/15285

Docs Related PR
https://github.com/umbraco/UmbracoDocs/pull/5650

I am glad to say that Jacob took on the feedback and has improved the customisation of the login screen, by allowing a custom CSS file to be loaded in and then allow further control of how this looks.

More CSS Control

It is now possible with the latest changes made by Jacob to add a CSS file to customise the look and feel further with your own CSS styling. To do this you can add a CSS file into a Package folder such as /App_Plugins/MyLoginScreen/styles.css and you will then need to add a package.manifest file to register the CSS placed at /App_Plugins/MyLoginScreen/package.manifest

{
  "css": [
    "~/App_Plugins/MyLoginScreen/styles.css"
  ],
  "bundleOptions": "None"
}

With this we are able to use some of the CSS variables that this login WebComponent exposes and can control hiding the pink curved SVG shape over the image and various other parts of the design and layout.

:root {
  // Hide the SVG shaped curves
  --umb-login-curves-display: none;

  // Set the color and transparency of the SVG shape
  --umb-login-curves-color: rgba(0, 0, 0, 0.4); 
}

The full list of CSS properties is available in the documentation.

Here is an example that tries to make the login screen look like previous versions

umb-auth {
        --umb-login-image-display: none;
        --umb-login-content-display: flex;
        --umb-login-content-width: 500px;
        --umb-login-content-background: rgba(255, 255, 255, 0.95);
        --umb-login-content-height: fit-content;
        --umb-login-align-items: center;
        --umb-login-background: url(https://raw.githubusercontent.com/umbraco/Umbraco-CMS/release-10.7.0/src/Umbraco.Web.UI.Client/src/assets/img/login.jpg) no-repeat center center/cover;
}

Add the quirky days of the week greetings back

The language keys still can be loaded for a more personalised message for every day of the week, but Umbraco will ship the same login message. To customise this you can add Umbraco language files . This needs to be a file at /config/lang/en_us.user.xml and add the following keys to have the quirky days of the weeks greetings back or alternatively change them to suit your own needs.

Note: You can not override these values shipped from Umbraco core by creating a language XML file in App_Plugins package folder.

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<language alias="en_us" intName="English (US)" localName="English (US)" lcid="" culture="en-US">
  <creator>
    <name>The Umbraco community</name>
    <link>https://docs.umbraco.com/umbraco-cms/extending/language-files</link>
  </creator>
  <area alias="login">
    <key alias="greeting0">Happy super Sunday</key>
    <key alias="greeting1">Happy marvelous Monday</key>
    <key alias="greeting2">Happy tubular Tuesday</key>
    <key alias="greeting3">Happy wonderful Wednesday</key>
    <key alias="greeting4">Happy thunderous Thursday</key>
    <key alias="greeting5">Happy funky Friday</key>
    <key alias="greeting6">Happy Caturday</key>
  </area>
</language>