Logging in Managed Packages

Issues with debugging when you install a managed package into the org?

How to build your own logging system.

A fairly common activity in Salesforce is to create managed packages. There are a few reasons for this. First, it makes it easy to deploy updates to multiple clients. Second, it allows you to protect your intellectual property by preventing your code from being seen or modified. Finally, it is a requirement for listing your package on the AppExchange.

You can create these packages very easily or you can go through a full security review from Salesforce. This review process can be very difficult and there is a lot involved in it, which include checking code for vulnerabilities (which is good to do anyway), making sure sensitive data is hidden from the correct people, making sure all code is bulkified, and ensuring that any external systems that connect with the Salesforce instance are also protected from vulnerabilities. In addition to this, there is a hefty annual fee to post your product on the AppExchange. These things can be daunting and may make you decide that you are better off not doing it. In the case that you don’t complete a security review, it will become very difficult to debug in client orgs. You cannot obtain access to debug logs of managed packages without the security review. This allows the client org to allow you to access the logs in the system. I devised a way in order to add some custom logging into your package that can be turned off and on without all of this extra headway.

Your first step is creating a new object to store these logs in. The simplest setup would be to make an auto numbered name and a long test text area for your logging message. (Keep in mind how long you make this restriction because your code can’t can not exceed it.

Error Log Object for storing logs
Figure 1 – Error Log Object
Error log fields
Figure 2 – Error Log Fields

You will also need a way to denote whether your logging is off enabled or on disabled because you don’t want to be creating lots of records that you will likely never look at. You really only want logging on when you are actually looking for themlogsthem. You should create a new List custom setting with a sufficient name such as ‘ErrorLogControl’. It just needs to contain a single field ‘IsLogging’ in order to denote whether logs are enabled or disabled.

Error log control
Figure 3 – New List custom setting

[su_spacer]
The class starts out like any other class except for one important change. This class should start off as pretty much any standard class except for one change. You want to specify It is important to define the class with the ‘without sharing‘ keywords so that no matter what User you are logged in as, the logs still get created.

public without sharing class ErrorLogControl
{

You only want to check if logging is on once so that you don’t hit governor limits and is just generally efficient. This ensures that you don’t do that. Once you check for logging, the variable gets stored for the next check. If it’s empty, then the code knows that it needs to populate it.

private static boolean isLogsOn
{
	private get
	{
		if (isLogsOn == null)
		{
			List<ErrorLogControl__c> control = ErrorLogControl__c.getAll().values();
			//If the custom setting is not here, assume logs should be off
			if (control.isEmpty())
				isLogsOn = false;
			else
				isLogsOn = control[0].IsLogging__c;
		}
		return isLogsOn;
	}
	private set;
}

Code should not be allowed to access the logs any way on this class besides how the class dictates. Declaring this as ‘private static’ allows nothing but only this class to touch it. That way you can’t have special circumstances that another developer wants to bake into the code, which can get confusing fast. This keeps everything controlled.

//Somewhere to store this list of logs that is hidden
private static List<Error_Log__c> eLogs
{
	private get
	{
		if (eLogs == null)
		{
			eLogs = new List<Error_Log__c>();
		}
		return eLogs;
	}
	private set;
}

This controls the actual creation of the logs. The code checks to see if you have logging on, and if so tells it go ahead and create and, if so, creates the log.

public static void addLog(String message)
{
	if (isLogsOn)
	{
		forceLog(message);
	}
}

The other addLog method uses this the forceLog method to actually create the logs. This is where you can add some flexibility into your own code. Say for instance you wanted to track something special, you can add it in here. The reason I have a second method for doing this, is in case you want to do some logging regardless if you have logging turned on or not. This is useful when an error in the code happens and you want to be able to see the issue regardless if they the user had the logs on. That way you don’t have to turn on logs and replicate the caught error. In this instance here, I added a ‘Created_Time__c’ custom field so that I can sort by the time of execution instead of the CreatedDate (which only tracks date) or the CreatedBy (which will sort by User then the CreatedDate and then the Timecreatedtime).

public static void forceLog(String message)
{
	eLogs.add(new Error_Log__c(Created_Time__c = DateTime.now(), Error_Messages__c = message));
}

This is one of the harder pain points of this setup. The logs need be added to the database at the end of whatever execution of code is going on. This is one of the harder pain points of this setup. Because of that, you might actually have to commit the logs multiple times. For example, you have work going on in a VF page, that will commit logs at the end of an action, but you also have the commitLogs() at the end of a trigger in case they change data through a standard layout. Because of this case, this method needs to make sure it clears the current set of logs each commit so that it isn’t trying to insert the same log twice causing an error and then other logs to fail on insertion. The rest could be handled however you desire. Originally, I only had the check for empty an empty list and insertion of the records into the database with the clearing of the list afterwards. That is the bare minimum you should have. I added some extra result parsing and exception handling though. These could let me add another log basically to say ‘hey these other logs did not insert, something is wrong’.

public static void commitLogs()
{
	if (!eLogs.isEmpty())
	{
		try
		{
			List<Database.SaveResult> results = Database.insert(eLogs, false);
			for (Database.SaveResult res: results)
			{
				if (!res.isSuccess())
				{
					//Handle this how you want
				}
			}
		} catch (Exception e)
		{
			//If for some reason it errored while logging
		}finally
		{
			//So that we don’t accidentally insert the same logs twice for whatever reason.
			eLogs.clear();
		}
	}
}

Full code below:

	/*
	Some lazy loadingcaching here so that we only check this once and then store the values for later.
	*/
	private static boolean isLogsOn
	{
		private get
		{
			if (isLogsOn == null)
			{
				List<ErrorLogControl__c> control = ErrorLogControl__c.getAll().values();
				//If the custom setting is here, assume logs should be off
				if (control.isEmpty())
					isLogsOn = false;
				else
					isLogsOn = control[0].IsLogging__c;
			}
			return isLogsOn;
		}
		private set;
	}

	//Somewhere to store this list of logs that is hidden
	private static List<Error_Log__c> eLogs
	{
		private get
		{
			if (eLogs == null)
			{
				eLogs = new List<Error_Log__c>();
			}
			return eLogs;
		}
		private set;
	}

	//Pretty self explanatory.  You only want
	public static void addLog(String message)
	{
		if (isLogsOn)
		{
			forceLog(message);
		}
	}

	//This method is reserved for when you want to log regardless of what the settings are.  Such as an error from a DML that you catch or something like that.
	public static void forceLog(String message)
	{
		eLogs.add(new Error_Log__c(Created_Time__c = DateTime.now(), Error_Messages__c = message));
	}

	//Put this at the end of your execution of code.
	public static void commitLogs()
	{
		if (!eLogs.isEmpty())
		{
			try
			{
				List<Database.SaveResult> results = Database.insert(eLogs, false);
				List<Error_Log__c> tempLogs = new List<Error_Log__c>();
				for (Database.SaveResult res: results)
				{
					if (!res.isSuccess())
					{
						//Handle this how you want
					}
				}
			} catch (Exception e)
			{
				//If for some reason it errored while logging.  You can add this error into a log if desired.
			} finally
			{
				//So that we don’t accidentally insert the same logs twice for whatever reason.
				eLogs.clear();
			}
		}
	}
}

3 thoughts on “Logging in Managed Packages”

  1. Pingback: Logging in Managed Packages | My CMS

    1. Damien Phillippi

      Oops. That was likely some specific addition I had made to another project that accidentally slipped in. Updated post to remove that.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top