Logging in Salesforce Commerce Cloud

I’ve been learning SFCC recently, and found that I was missing a simple guide for server logging, so I decided to write one.

Basic Usage
First you need to get a reference to a Log object, and then you issue log statements that are the level you want (debug, info, warn, error, fatal):

var Logger = require('dw/system/Logger');
var log = Logger.getLogger('my_category');
log.warn('createRequest call took {0}ms, which is more than the threshold of {1}ms', 999, 500);

By passing just one argument to getLogger(), you will be putting your log statements in the default location – more on that below.

Finding Log Entries
There are two ways to access logs, through Business Manager (BM), neither of which are immediately obvious:

  1. The log center
  2. The actual log files

The log center is accessed in Administration / Site Development / Development Setup, although it requires specific permissions:

logcenter

The other way is to access the log files directly – also via BM – in Administration / Site Development / Development Setup, then click on Folder Browser, and navigate to the Logs directory:

folder_browser

Log Files
If you initialize the log object with one argument (e.g. getLogger('my_category')), then you are specifying the category only, and the log entries will appear in a generic file. The file will be named something like customwarn-ecom-sandbox-SANDBOX-appserver-DATE.log where “warn” is the log level.

The category will appear in the log entry:

[2021-03-23 23:46:32.086 GMT] WARN PipelineCallServlet|212645572|SITE_NAME|CONTROLLER_NAME|PipelineCall|iEHTL3fGnn custom.my_category [] createRequest call took 999ms, which is more than the threshold of 500ms

You can add a second parameter when getting the log object, which will create a new log file. This may be useful if you are developing a cartridge and want all of that cartridge’s logging information stored in one place:

var log = Logger.getLogger('my_cartridge', 'my_category');

That will create a dedicated log file like so, in the same location as the other log files:

custom-my_cartridge-ecom-SANDBOX-ITENTIFIERS-DATE.log

Enabling Log Levels
The log level you want to use must be enabled in BM under Administration / Operations / Custom Log Settings, in the Custom Log Targets section:

enabled

If it’s not, then the log entries won’t appear.

Writing a Grunt Plugin that Uses other Plugins

I’ve been using Grunt for a while now, and wanted to package some reusable functionality from one project into a plugin, so that it could easily be used in other projects. For some reason, this turned out to be a lot less intuitive than I was expecting. I read the official page on Creating Plugins, but it doesn’t mention a variety of details you need to pay attention to if you want to encapsulate other plugins within your own plugin.

There are several gotchas. First, if you simply port the 3rd-party tasks’ configurations to the plugin’s Gruntfile.js, your plugin will work when you test it (from the plugin source), but it will fail when you use your plugin, since the plugin’s Gruntfile doesn’t get processed when it is run as a plugin. You also have to explicitly load tasks you want to use, and be mindful of how you use paths to reference them.

Here are my tips on writing plugins:

  1. Clone the grunt-init gruntplugin repository and run grunt-init gruntplugin to setup the plugin template, as described in the Grunt documentation on creating plugins. This will be your plugin source. You’ll be able to run Grunt in there to test it.
  2. Right away, add your plugin to the project you wish to use it in, so that you can test it as a plugin while you develop. You don’t need to publish it, instead, in your project package.json, add it as a devDependency with a URL to its GIT repository:
    myproject/package.json
      "devDependencies": {
        "grunt-myplugin": "git@bitbucket.org:antun/grunt-myplugin.git"
      }

    As you make updates, you’ll need to commit/push, then uninstall/reinstall the plugin from your project as follows:

    $ npm rm grunt-myplugin
    $ npm install
  3. Add your 3rd-party plugins to your plugin source’s package file as dependencies, rather than devDependencies. This will ensure they’re installed under your project when your plugin is installed.
    package.json
      "dependencies": {
        "grunt-shell" : "1.1.1"
      },
  4. Explicitly load the 3rd party task you want to use, from your plugin source’s task file. This is necessary since your plugin won’t automatically have access to its own dependencies, when it’s used as a plugin in another project. Do this near the top of the task file. e.g. If you rely on the grunt-shell module within your plugin, would do:
    tasks/buttonimage.js
    module.exports = function(grunt) {
      var path = require('path');
      grunt.task.loadTasks(path.resolve(__dirname, '../node_modules/grunt-shell/tasks'));
      // load any other tasks, and task registration
    }

    Note also that I used the Node.js path.resolve() method to handle loading the URL relative to the plugin, no matter where it’s run from. Since path is a core Node.js module, you can require it without any special dependency handling.

  5. Define your plugin’s custom task(s) in its source Gruntfile. Organize the options as you would like to see them used when it is used as a plugin. Remember, this configuration will only be used when you run your plugin’s tasks from within the source.
    Gruntfile.js
      grunt.initConfig({
        buttonimage: {
          testButton: { 
              output: 'dist/testButton.gif'
          }
        }
        /* Other configuration, test, stuff */
      }

    You can use this.options() inside of your

  6. Don’t bother adding configuration for your 3rd-party tasks in the plugin source Gruntfile. The source Gruntfile doesn’t get processed when you run your plugin in another project. Instead, set the configuration procedurally from within your task file using grunt.config.set() before running the task. e.g.
    tasks/buttonimage.js
      grunt.registerMultiTask('buttonimage', 'Generate localized buttons with text labels.', function() {
        var cmd = 'some shell command you built up';
        grunt.config.set('shell', {
            buttonImage: {
                command: cmd
            }
          }
        );
        grunt.task.run(['shell:buttonImage']);
      });
  7. Don’t change the working directory in your task, so work with absolute paths instead. Use path.resolve() to get an absolute path relative to the top-level Gruntfile for files that you will want to operate on. When you define file paths in the Gruntfile, whether that’s the one in your plugin source config or eventually the one in the project where you will use your plugin, you will want to convert them to absolute paths. To do this, use the Node.js path.resolve() method to convert the input path – which is relative to the root Gruntfile – to an absolute path:
    tasks/buttonimage.js
      grunt.registerMultiTask('buttonimage', 'Generate localized buttons with text labels.', function() {
        var outputPath = path.resolve(this.options().output);
        var cmd = "convert -some arguments " + outputPath
        /* Set the config and run your task */
      }

    If you need to get an absolute path relative to your task definition (e.g. to run a Node.js script that you have bundled inside your plugin), you can use path.resolve(__dirname, ‘path/relative/to/task/file.js’) to get that.

Hopefully these will save you some time!

Arduino Stopwatch Experiment

I’ve been working my way through the lessons in the SunFounder LCD Ultrasonic Relay Sensor Electronic Bricks Starter Kit. The kit comes with a manual, and the early experiments have pretty detailed steps with electronics diagrams.

But by the time you get to the stopwatch experiment (lesson 13) there are no more diagrams, so I was left to figure out how to connect the 4-digit 7-segment display. My display had the part number SMA420564, and it had 12 pins. I couldn’t find a spec sheet for it, to understand which pins referred to which segment/digit. After a bit of trial and error, I figured it out:

Pin Diagram for SMA420564

Pin Diagram for SMA420564

The way I figured it out which pin mapped to which was to adapt the example program that came with the kit to light all segments at once (i.e. “8.”) and then plug/unplug wires until I figured out which was which. I don’t really know the pin numbers, but as long as you know which digit/segment they light, that’s enough. The pins d1, d2, d3, and d4 correspond with each digit, with d1 being the first on the left. The pins with capital letters correspond with the segments A-G of the 7-segment character. The naming convention appears to be quite standardized. The diagram below shows which letter corresponds with which segment:

7-segment-display-labeled

Here’s a wiring schematic to show which pins of the Arduino each pin goes to. Note that in the diagram below, the LED display is rotated 90º clockwise:

Timer_Schematic

Schematic for Experiment 13 in Sunfounder Ultrasonic Kit for Arduino

I used Upverter to create the diagram, and you can access it directly on the Upverter site.

With everything wired, it was time to try the code. The kit I had came with one of those small CDs, so I actually downloaded it from the Sunfounder web site. It’s lesson #13 in the Ultrasonic Kit for Arduino here. (NOTE: The original URL doesn’t work, but I found a ZIP of the entire set of lessons for the Ultrasonic Kit, and also posted the source for lesson #13 as a Gist.) When I tried to validate the stopwatch, it failed with the following error:

Timer1 was not declared in this scope

Turns out that there are some C++ libraries that the Sunfounder example code relies on, which weren’t in my environment. I found this blog post that talks about the error, and there’s a link to download the TimerOne library. After downloading it I wasn’t really sure what to do with the .h and .cpp files. I tried placing them in the same directory as my .ino file, but that didn’t help. Eventually I figured out that you have to import it via the Arduino IDE (Sketch>Import Library>Add Library… and then select the folder that contains the TimerOne.cpp and TimerOne.h files.

With that done, I was able to burn the program to my Arduino, and now have a happy stopwatch:

stopwatch