Sitecore Auditing with the Advanced System Reporter
I originally published this post on the Igloo blog.
Recently we had a request from one of our clients to create a tool that will successfully audit the changes made by Sitecore users. The auditing system would need to display the items that were created, changed and published in the week that had passed.
Initially we started by using the Advanced System Reporter from the Sitecore Shared Source Library, and tested out the ‘Item History’ report that it generates. After looking into it, I found that the report was falling short of what we desired.
Warning:- The rest of this post is fairly technical, and assumes you’ve installed and have some understanding of the Advanced System Reporter in Sitecore.
The Problem
Here’s what I found:-
- Items that were published by the user publishing one of their ancestors and ticking ‘Publish Subitems’ (i.e deep publish) would not show that they were published.
- Items that were published didn’t display which database they were being published to (so we didn’t know if it was a publish to staging or to production).
- Items would be shown to be published if the user clicked on the publish button, but then didn’t proceed to publish, and just quit out of the wizard.
- Item history only seemed to go back a couple of days, where we needed to display a week’s worth of changes.
Figuring out the issue
The Advanced System Reporter is designed to be easily extendable. The way it works is you choose a set of ‘Scanners’ that will find the information that you’re after, and then choose a set of ‘Viewers’ that will display your data. You can take it further by using Filters and Commands as well, but for the sake of space, we’ll leave them out for now.
To see what scanners and viewers your report uses, navigate to /sitecore/system/Modules/ASR/Reports and find your report. The ‘Item History’ report used the AuditPerItem scanner (/sitecore/system/Modules/ASR/Configuration/Scanners/AuditPerItem) to fetch the data. Investigating this item, we can see that the scanner uses the ASR.Reports.Items.AuditPerItem class in the ASR.Reports DLL.
Using ILSpy to decompile the Advanced System Reporter DLLs (which I didn’t need to do because the project is open source) I found that this class was using the LogScanner class which was trolling the logs for the information. In the logs, it was utilising all of the Log.Audit() calls to filter the data.
The Solution – Part A) Writing out your own log entries
You will need to find the places in your code where certain events occur, or are captured, and log your own information for auditing. These are best found in Events, Commands, and Pipelines. For this example, we are going to write a log entry whenever an item is published.
Create a new class with a function that will be triggered when an item is published. We will log audit information about the published item for our report. Use the following:-
public class HistoryTraceEvent { public void OnItemPublished(object sender, EventArgs args) { try { ItemProcessedEventArgs theArgs = args as ItemProcessedEventArgs; Item currentItem = theArgs.Context.PublishHelper.GetSourceItem(theArgs.Context.ItemId); string targetDbName = ""; Sitecore.Data.Database db = Sitecore.Data.Database.GetDatabase("master"); List<string> pubTargets = theArgs.Context.PublishContext.PublishOptions.PublishingTargets; if (pubTargets.Any()) { ID targetDB_ID = new ID(pubTargets[0]); Item targetDBItem = db.GetItem(targetDB_ID); targetDbName = targetDBItem.Name; } // log the item details, appending the target database on the end of the message Log.Audit("HistoryTrace: Publish item: " + AuditFormatter.FormatItem(currentItem) + ", target: " + targetDbName, this); } catch (Exception e) { Log.Error("Could not correctly retrieve the published item in Sitecore. " + e.ToString(), this); } } }
Now make your class listen to the publish event by adding it to the <sitecore><events> node in web.config (or an attached Include config file). Make it listen to the ‘publish:itemProcessed’ event, which will be triggered just after the item is published.
<event name="publish:itemProcessed"> <handler type="Sitecore.Igloo.Kernel.Events.HistoryTraceEvent, Sitecore.Igloo.Kernel" method="OnItemPublished" /> </event>
The Solution – Part B) Config Changes
I decided to keep the functionality very similar, but to store the history data I was after in a separate log file. This way, I could manage when a log entry was written, and also, what information was being written to it. Another bonus was that it wasn’t going to blow out my normal log files, and make debugging a nightmare.
To do this, I set up a new log file using Benjamin Vangansewinkel’s Blog Post and editing the web.config file.
In the <log4net> node:-
Create a new log appender that will accept anything containing the text ‘HistoryTrace’:-
<appender name="MyFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging"> <file value="$(dataFolder)/logs/log.historytrace.{date}.txt" /> <filter type="log4net.Filter.StringMatchFilter"> <regexToMatch value=".*HistoryTrace.*" /> </filter> <filter type="log4net.Filter.DenyAllFilter" /> <appendToFile value="true" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%4t %d{ABSOLUTE} %-5p %m%n" /> </layout> </appender>
Add the appender:-
<root> <priority value="INFO"/> <appender-ref ref="LogFileAppender"/> <appender-ref ref="MyFileAppender" /> </root>
Change the normal log file appender to ignore anything containing the text ‘HistoryTrace’:-
<appender name="LogFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging"> <file value="$(dataFolder)/logs/log.{date}.txt"/> <filter type="log4net.Filter.StringMatchFilter"> <regexToMatch value="^((?!HistoryTrace).)*$" /> </filter> <filter type="log4net.Filter.DenyAllFilter" /> <appendToFile value="true"/> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%4t %d{ABSOLUTE} %-5p %m%n"/> </layout> </appender>
Now make sure that the log files aren’t being automatically removed before you need them.
Find the
<sitecore> <scheduling> <agent type="Sitecore.Tasks.CleanupAgent" method="Run" interval="06:00:00">
node in the web.config…and look for the command
<remove folder="$(dataFolder)/logs" pattern="log.*.txt" ....>
This is the command that deletes your log files. Set your maxCount and minAge appropriately (I set them to 100 and 14.00:00:00 respectively, to give me two weeks worth of information.
The Solution – Part C) The Code
The main issue to overcome here is to make the LogScanner use the new historytrace logs you’ve created, instead of the regular log files. To do this, create a class that replicates the ASR.Reports.Logs.LogScanner class in ASR.Reports. Call it HistoryLogScanner. Simply change the DEFAULT_PATTERN value to “log.historytrace.*.txt”. Further down, where you see
list.Add(LogItem.Make(date, match.Groups["pid"].Value, match.Groups["type"].Value, match.Groups["text"].Value));
remove that line and replace it with:-
list.Add(LogItem.Make(date, match.Groups["pid"].Value, match.Groups["type"].Value, match.Groups["text"].Value.Replace("HistoryTrace: ", "")));
All we are doing here is removing the words ‘HistoryTrace’ for each log entry.
Now, create a class that replicates the ASR.Reports.Items.AuditPerItem class in ASR.Reports. Call it HistoryAuditPerItem All we want to change here is the Scan function. Find the line that creates a new instance of the LogScanner, and replace it to use your new HistoryLogScanner class (that you just created above.) Compile your code and make sure everything is good to go. (You will probably have to add references to the ASR and ASR.Reports DLLs in your Visual Studio Solution.)
The Solution – Part D) Setting up your report in Sitecore
Find the AuditPerItem Scanner in Sitecore (/sitecore/system/Modules/ASR/Configuration/Scanners/AuditPerItem) and duplicate it. On the duplicate, change the Assembly and Class fields to reference your new scanner, HistoryAuditPerItem.
Now, find the Item History report item in Sitecore (/sitecore/system/Modules/ASR/Reports/Item History) and duplicate it. On the duplicate, remove the ‘AuditPerItem’ scanner from the ‘Scanners’ field, and add your new ‘HistoryAuditPerItem’ scanner. Save the item, publish some items for testing….and check out your new auditing report!
Taking it Further
As discussed earlier, you can log information on all types of events, commands and pipelines, not just when an item is published. Here are some useful ones you may want to tap into:-
Events – http://sdn.sitecore.net/Articles/API/Using%20Events/Handling%20Events.aspx
item:saved
item:sortorderchanged
item:moved
item:deleted
item:created
item:added
item:copied
Commands
item:setlayoutdetails – Sitecore.Shell.Framework.Commands.SetLayoutDetails.Run
contenteditor:edit –
– item:checkout –
– item:startedediting – ???
item:checkin – Sitecore.Shell.Framework.Commands.CheckIn.Run
item:pastefromclipboard – Sitecore.Shell.Framework.Commands.PasteFromClipboard.Run
Pipelines
uiDuplicateItem – Sitecore.Shell.Framework.Pipelines.DuplicateItem.ExecuteuiRenameItem – Sitecore.Shell.Framework.Pipelines.RenameItem.Execute
Happy Auditing!
Hi! Probably a dumb question but where can I get the ASR dlls from? I am getting errors trying to build the repo locally to generate my own dlls. A whole bunch of TDS items missing 🙁
I suppose you could download the package from the Marketplace (https://marketplace.sitecore.net/en/Modules/Advanced_System_Reporter.aspx) and extract the DLLs from there.
I assume you were trying to use the Github repo (https://github.com/rauljmz/AdvancedSystemReporter)…. What items were missing from the TDS project?