ludodepaz
Contributor

When I made the move to OneStream, one of the first things I understood and appreciated was logging. It is such a welcome change from legacy systems! You can easily log anything: text, the value of a variable, data, you name it.

After a few years of logging like a maniac, however, I saw three main shortcomings in the default implementation:

  1. Logging is popular, so we can easily clog the Error Log with debugging messages, especially in large projects where many developers are working at the same time.
  2. We forget to comment logs and the debugging logs make their way to production.
  3. Some objects are impossible or too complicated to log with simple toString calls.

In this brief series of posts, I will provide a few suggestions on how to improve your logging to address these issues. As you know, in OneStream there is always one more way to do anything, so there might well be other approaches! Feel free to talk about it in the comments.

Log in a file instead of the Error Log

This resolves pain point #1: clogging the Error Log, particularly in complex implementations with lots of developers and lots of different machinery in play.

Initial Setup

To log into a file, you need to add a Public Function; why a Public Function and not a simple rule snippet? Because you will probably want to use this Function across many different rules and call it any time you want to log into a file. In order to do that, you will also have to make sure the property ‘Contains Global Functions For Formulas’ is set to ‘True’ for the rule that will contain this function.

ludodepaz_0-1706629857602.png

The full rule is available in the attached zip, but let's have a quick look at what it looks like.

Public Shared Function WriteLogger (ByVal si As SessionInfo, _
		ByVal api As Object, ByRef logger As System.Text.StringBuilder, _
		Optional ByVal fileName As String = "LOG", _
		Optional ByVal folderName As String = "TestLogs", _
		Optional ByVal fileSuffix As String = Nothing) As String

	' Saves log to text file on application database
	' 
	' Parameters
	' 	logger: string builder object with contents of log file
	' 	fileName: optional log file name. defaults to LOG
	' 	folderName: optional log folder destination. defaults to TestLogs
	'	fileSuffix: optional log file suffix. defaults to system time
	' Returns
	' 	filepath of log file
	
	[ ... various setup machinery ... ]

	Dim dbFile As New XFFile(dbFileInfo, String.Empty, _
		System.Text.Encoding.UTF8.GetBytes(logger.ToString))
	BRApi.FileSystem.InsertOrUpdateFile(si, dbFile)

	Return dbFile.FileInfo.FullName
End Function

As you can see, the rule will create an XFFile object, populating it with text coming from a StringBuilder (which is an object optimized to stitch a lot of text together). We will call this rule to send our messages to a file instead of the Error Log.

Rule Configuration

In order to use this helper rule, we will need to import the Global Function; this is done in just a few steps:

  1. Reference the Rule "file" (namespace) in the Properties of the Rule that will use it. Note how you can hover your cursor over "Referenced Assemblies" for a detailed explanation.

ludodepaz_1-1706629857608.png

  1. Import the namespace

This is done at the very top of the target rule:

ludodepaz_2-1706629857609.png

  1. Then, you need to declare 2 variables:

ludodepaz_3-1706629857611.png

The first Variable ‘bVerboseLogging’ is here to tell OneStream to log into a file or not. This is a good idea, because we can turn this off when we promote a rule to Production for example. We will come back to that. The second Variable ‘logger’ will contain our actual log messages.

Logging to a file

The initial setup and the configuration can seem a little overwhelming the first time, but you quickly forget about it once you start logging. To log into a file instead of the Error Log, use this call instead of api.logmessage or brapi.errorlog.logmessage:

Let’s try it out!

So, let’s say we want to write “Hello World” to the log, you will do the following:

logger.AppendLine("Hello World").AppendLine()

Basically, we are writing the log to a text string, now we need to write this string to a file in the File Explorer:

JackLacava_0-1710517089092.png

In our case, we are writing to a file named ‘Logs’ and in a folder named ‘TestLogs’

JackLacava_1-1710517138719.png

And you got it, your logs are in a file now!

JackLacava_2-1710517165883.png

Log or don’t log with a Boolean

We all put logs everywhere in our code and it’s normal to forget to delete or comment out them but it becomes a problem when the active logs make their way to production:

  1. It adds unnecessary information to the Production Error Log
  2. It costs performance (you are writing a row in a database table for every message)

This is why a MarketPlace Solution like System Diagnostics alerts you if logging is active in your code.

When you log your messages into a file, you’re not hurting performance as if you write in the Error Log (because you don’t hit the database) but at the same time, why would you add pointless files in FileShare? A good solution is to create a global Boolean switch and only write to the logfile when it's set to True.

Do you remember when we created variables to log into a file? One of the variables was bVverboseLogging; this one can be used to determine whether we want to log or not:

JackLacava_6-1710517876651.png

Whenever we log (to a file or the Error Log), we add a condition on this variable.

JackLacava_5-1710517832492.png

Note how we can simplify the test on the Boolean variable.

With that, when you move to Production, all you have to do is set the bVerboseLogging variable to False in the source rule, it won’t write anything to the logs.

We can even go further by setting the value of this variable in the DataManagement job, for this, pass the value in the DM Job:

JackLacava_7-1710517966020.png

And in your Business Rule, in CustomCalculate, set the value from the argument:

JackLacava_8-1710518035229.png

Now, you can ask the Administrator to set the value to True or False from the DM Job, without opening the Business Rule.

Bonus: Log only when an error occurs

In Production Environments, to be honest, in most cases you don't really want to actually log anything - unless an unfortunate event occurs. In those situations you want to know exactly what happened, to figure out what the problem was, and nothing does it better than a gorgeous log message. So how can we do it?

When you look at the structure of a Business Rule, after the ‘End Select’, there is a section to deal with Exceptions. By default, OneStream will throw an error and write the error in the Error Log, but nothing prevents you from adding more good stuff! This is what we will do:

JackLacava_3-1710517510483.png

This works very well when you pair it with logging to a file. Once, I was building a dimension based on another one, and I was able to log precisely when there was an error, identifying the problematic members. The same applies with a seeding rule, where you can have issues because of missing members.

Conclusion

This post provided a logging framework that you can use to improve your logging practices, or as an inspiration to write your own. We covered how to log to a file instead of the database, and some strategies to deal with logging in Production. However, the topic of logging is vast: what about logging complex objects, rather than simple text? What about integrating in the .Net machinery? Stay tuned...