# Migration Guide

    ## Registered S3 method overwritten by 'log4r':
    ##   method         from
    ##   print.loglevel logger

In this vignette I suppose that you are already familiar with at least one of the similar logging R packages and you are looking for suggestions on how to switch to `logger`. Before moving forward, please make sure that you have read the Introduction to logger, The Anatomy of a Log Request and Customizing the Format and the Destination of a Log Record vignettes for a decent background on `logger`, and use this vignette as a quick-reference sheet to help you migrate from another package.

## futile.logger

The `logger` package has been very heavily inspired by `futile.logger` and have been using it for many years, also opened multiple pull requests to extend `futile.logger` before I decided to revamp my ideas into a new R package – but there are still many common things between `futile.logger` and `logger`.

### Initialize

Both packages comes with a default log engine / config, so it’s enough to load the packages and those are ready to be used right away:

futile.logger

    library(futile.logger)
    #>
    #> Attaching package: 'futile.logger'
    #> The following objects are masked from 'package:logger':
    #>
    #>     DEBUG, ERROR, FATAL, INFO, TRACE, WARN

logger

    library(logger)

### Logging functions

The most important change is that function names are by snake_case in `logger`, while `futile.logger` uses dot.separated expressions, and `futile.logger` prefixes function names by `flog` while `logger` uses `log` for that:

futile.logger

    flog.info('hi there')
    #> INFO [2024-03-05 15:10:46] hi there
    flog.warn('watch out')
    #> WARN [2024-03-05 15:10:46] watch out

logger

    log_info('hi there')
    #> INFO [2024-03-05 15:10:46] hi there
    log_warn('watch out')
    #> WARN [2024-03-05 15:10:46] watch out

As you can see above, the default layout of the messages is exactly the same.

### Log levels

Regarding log levels, `futile.logger` bundles the default `log4j` levels (`TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR` and `FATAL`) that is extended by `SUCCESS` in `logger` as sometimes it’s worth logging with a higher than `INFO` level that something succeeded.

### Log record layout

Changing layouts is easy in both package, as you simply pass a layout function:

futile.logger

    flog.layout(layout.json)
    #> NULL
    flog.info('hi again')
    #> {"level":"INFO","timestamp":"2024-03-05 15:10:46 +0100","message":"hi again","func":"tools::buildVignettes"}

logger

    log_layout(layout_json())

    log_info('hi again')
    #> {"time":"2024-03-05 15:10:46","level":"INFO","ns":"global","ans":"global","topenv":"R_GlobalEnv","fn":"eval","node":"nevermind","arch":"x86_64","os_name":"Linux","os_release":"6.7.6-arch1-1","os_version":"#1 SMP PREEMPT_DYNAMIC Fri, 23 Feb 2024 16:31:48 +0000","pid":157305,"user":"daroczig","msg":"hi again"}

As you can see, `logger` provided a bit more information about the log request compared to `futile.logger`, but it’s easy to change the list of fields to be used in the JSON – see `?get_logger_meta_variables` for a complete list of variable names to be passed to `?layout_json`. `logger` also ships a lot more layouts, eg `?layout_glue_colors` or roll out your own via the `?layout_glue_generator` factory function.

### Log message formatting

By default, `futile.logger` uses an `sprintf` formatter, while `logger` passes the objects to be logged to `glue`:

futile.logger

    flog.info('hi')
    #> INFO [2024-03-05 15:10:46] hi
    flog.info('hi %s', 84/2)
    #> INFO [2024-03-05 15:10:46] hi 42
    flog.info(paste('hi', 84/2))
    #> INFO [2024-03-05 15:10:46] hi 42
    flog.info(glue::glue('hi {84/2}'))
    #> INFO [2024-03-05 15:10:46] hi 42

logger

    log_info('hi')
    #> INFO [2024-03-05 15:10:46] hi
    log_info('hi {84/2}')
    #> INFO [2024-03-05 15:10:46] hi 42
    log_formatter(formatter_sprintf)
    log_info('hi %s', 84/2)
    #> INFO [2024-03-05 15:10:46] hi 42
    log_formatter(formatter_paste)
    log_info('hi', 84/2)
    #> INFO [2024-03-05 15:10:46] hi 42

It’s easy to change this default formatter in both packages: use `flog.layout` handles this as well in `futile.logger`, while the formatter is separated from the layout function in `logger`, so check `?log_formatter` instead. `logger` ships with a bit more formatter functions, eg the default `?formatter_glue` and `?formatter_glue_or_sprintf` that tries to combine the best from both words.

### Log record destination

Setting the destination of the log records works similarly in both packages, although he `logger` packages bundles a lot more options:

logging

    t <- tempfile()
    flog.appender(appender.file(t))
    #> NULL
    flog.appender(appender.tee(t))
    #> NULL

logger

    t <- tempfile()
    log_appender(appender_file(t))
    log_appender(appender_tee(t))

### Hierarchical logging and performance

Both packages support using different logging namespaces and stacking loggers within the same namespace. Performance-wise, there’s `logger` seems to be faster than `futile.logger`, but for more details, check the Simple Benchmarks on Performance vignette.

### Using `logger` as a drop-in-replacement of `futile.logger`

`logger` has no hard requirements, so it’s a very lightweight alternative of `futile.logger`. Although the function names are a bit different, and the message formatter also differs, but with some simple tweaks, `logger` can become an almost perfect drop-in-replacement of `futile.logger`:

    library(logger)
    log_formatter(formatter_sprintf)
    flog.trace <- log_trace
    flog.debug <- log_debug
    flog.info <- log_info
    flog.warn <- log_warn
    flog.error <- log_error

    flog.info('Hello from logger in a futile.logger theme ...')
    flog.warn('... where the default log message formatter is %s', 'sprintf')

## logging

The `logging` package behaves very similarly to the Python logging module and so thus being pretty Pythonic, while `logger` tries to accommodate native R users’ expectations – so there are some minor nuances between the usage of the two packages.

### Initialize

In `logging`, you have to initialize a logger first via `addHandler` or simply by calling `basicConfig`, which is not required in `logger` as it already comes with a default log config:

logging

    library(logging)
    basicConfig()

logger

    library(logger)

### Logging functions

After initializing the logging engine, actual logging works similarly in the two packages – with a bit different function names:

- although `logging` uses mostly camelCase function names (eg `basicConfig`), but the logging functions are all lowercase without any separator, such as `loginfo` or `logwarn`
- `logger` uses snake_case for the function names, such as `log_info` and `log_warn`

logging

    loginfo('hi there')
    #> 2024-03-05 15:10:46.327209 INFO::hi there
    logwarn('watch out')
    #> 2024-03-05 15:10:46.338784 WARNING::watch out

logger

    log_info('hi there')
    log_warn('watch out')

As you can see above, the default layout of the log messages is somewhat different:

- `logging` starts with the timestamp that is followed by the log level, optional namespace and the message separated by colons
- `logger` starts with the log level, followed by the timestamp between brackets and then the message

### Log levels

For the available log levels in `logging`, check `?loglevels`, and `?log_levels` for the same in `logger`:

logging

    str(as.list(loglevels))
    #> List of 11
    #>  $ NOTSET  : num 0
    #>  $ FINEST  : num 1
    #>  $ FINER   : num 4
    #>  $ FINE    : num 7
    #>  $ DEBUG   : num 10
    #>  $ INFO    : num 20
    #>  $ WARNING : num 30
    #>  $ WARN    : num 30
    #>  $ ERROR   : num 40
    #>  $ CRITICAL: num 50
    #>  $ FATAL   : num 50

logger

    levels <- mget(ls(
        envir = environment(logger), pattern = '^[A-Z]'),
        envir = environment(logger))
    str(levels[order(-as.numeric(levels))], give.attr = FALSE)
    #>  Named list()

### Performance

Performance-wise, there’s no big difference between the two packages, but for more details, check the Simple Benchmarks on Performance vignette.

### Log record layout

Getting and setting the layout of the log record should happen up-front in both packages:

logging

    getLogger()[['handlers']]$basic.stdout$formatter
    #> function (record)
    #> {
    #>     msg <- trimws(record$msg)
    #>     text <- paste(record$timestamp, paste(record$levelname, record$logger,
    #>         msg, sep = ":"))
    #>     return(text)
    #> }
    #> <bytecode: 0x595477b73b38>
    #> <environment: namespace:logging>

logger

    log_layout()
    #> layout_simple

`logger` provides multiple configurable layouts to fit the user’s need, eg easily show the calling function of the lof request, the `pid` of the R process, name of the machine etc. or colorized outputs. See Customizing the Format and the Destination of a Log Record vignette for more details.

### Log message formatting

If you want to pass dynamic log messages to the log engines, you can do that via the hard-coded `sprintf` in the `logging` package, while you can set that on a namespaces basis in `logger`, which is by default using `glue`:

logging

    loginfo('hi')
    #> 2024-03-05 15:10:46.423546 INFO::hi
    loginfo('hi %s', 84/2)
    #> 2024-03-05 15:10:46.425129 INFO::hi 42
    loginfo(paste('hi', 84/2))
    #> 2024-03-05 15:10:46.426593 INFO::hi 42
    loginfo(glue::glue('hi {84/2}'))
    #> 2024-03-05 15:10:46.428261 INFO::hi 42

logger

    log_info('hi')
    log_info('hi {84/2}')
    log_formatter(formatter_sprintf)
    log_info('hi %s', 84/2)
    log_formatter(formatter_paste)
    log_info('hi', 84/2)

For even better compatibility, there’s also `?formatter_logging` that not only relies on `sprintf` when the first argument is a string, but will log the call and the result as well when the log object is an R expression:

    log_formatter(formatter_logging)
    log_info('42')
    log_info(42)
    log_info(4+2)
    log_info('foo %s', 'bar')
    log_info(12, 1+1, 2 * 2)

### Log record destination

Setting the destination of the log records works similarly in both packages, although he `logger` packages bundles a lot more options:

logging

    ?addHandler
    ?writeToConsole
    ?writeToFile

logger

    ?log_appender
    ?appender_console
    ?appender_file
    ?appender_tee
    ?appender_slack
    ?appender_pushbullet

### Hierarchical logging

Both packages support using different logging namespaces and stacking loggers within the same namespace.

### Using `logger` as a drop-in-replacement of `logging`

`logger` has no hard requirements, so it’s an adequate alternative of `logging`. Although the function names are a bit different, and the message formatter also differs, but with some simple tweaks, `logger` can become an almost perfect drop-in-replacement of `logging` – although not all log levels (eg and ) are supported:

    library(logger)
    log_formatter(formatter_logging)
    log_layout(layout_logging)
    logdebug <- log_debug
    loginfo <- log_info
    logwarn <- log_warn
    logerror <- log_error

    loginfo('Hello from logger in a logging theme ...')
    logwarn('... where the default log message formatter is %s', 'sprintf', namespace = 'foobar')

## log4r

The `log4r` package provides an object-oriented approach for logging in R, so the logger object is to be passed to the log calls – unlike in the `logger` package.

### Initialize

So thus it’s important to create a logging object in `log4r` before being able to log messages, while that’s automatically done in `logger:

log4r

    library(log4r)
    #>
    #> Attaching package: 'log4r'
    #> The following object is masked _by_ '.GlobalEnv':
    #>
    #>     logger
    #> The following object is masked from 'package:logging':
    #>
    #>     levellog
    #> The following objects are masked from 'package:logger':
    #>
    #>     as.loglevel, logger
    #> The following object is masked from 'package:base':
    #>
    #>     debug
    logger <- create.logger(logfile = stdout(), level = "INFO")

logger

    library(logger)

Please note that in the background, `logger` does have a concept of logger objects, but that’s behind the scene and the user does not have to specify / reference it. On the other hand, if you wish, you can do that via the `namespace` concept of `logger` – more on that later.

### Logging functions

While `logger` has a `log_` prefix for all logging functions, `log4r` has lowercase functions names referring to the log level, which takes a logging object and the log message:

log4r

    info(logger, 'hi there')
    #> INFO  [2024-03-05 15:10:46] hi there
    warn(logger, 'watch out')
    #> WARN  [2024-03-05 15:10:46] watch out

logger

    log_info('hi there')
    log_warn('watch out')

As you can see the default layout of the messages is a bit different in the two packages.

### Log levels

Both packages are based on `log4j`, and `log4r` provides `DEBUG`, `INFO`, `WARN`, `ERROR` and `FATAL`, while `logger` also adds `TRACE` and `SUCCESS` on the top of these.

To change the log level threshold, use the `level` function on the logging object in `log4r`, while it’s `log_level` in `logger`.

### Log record layout and formatter functions

The `log4r` provides a `logformat` argument in `create.logger` that can be used to override the default formatting, while `logger` provides formatter and layout functions for a flexible log record design.

### Log record destination

By default, `log4r` logs to a file that can be set to `stoud` to write to the console, while `logger` writes to the console by default, but logging to files via the `appender_file` functions is also possible – besides a number of other log record destinations as well.

### Hierarchical logging and performance

Creating objects is the `log4r` way of handling multiple log environments, while `logger` handles that via `namespace`s.

## loggit

Sorry, no direct replacement for `loggit` – capturing `message`, `warning` and `stop` function messages, but it’s on the roadmap to provide helper functions to be used as message hooks feed `logger`.

    #> Warning: 'logger' namespace cannot be unloaded:
    #>   namespace 'logger' is imported by 'logger.tester' so cannot be unloaded