# 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 [2026-08-24 14:00:25] hi there
    flog.warn("watch out")
    #> WARN [2026-08-24 14:00:25] watch out

logger

    log_info("hi there")
    #> INFO [2026-08-24 14:00:25] hi there
    log_warn("watch out")
    #> WARN [2026-08-24 14:00:25] 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":"2026-08-24 14:00:25 +0200","message":"hi again","func":"tools::buildVignettes"}

logger

    log_layout(layout_json())

    log_info("hi again")
    #> {"time":"2026-08-24 14:00:25","level":"INFO","ns":"global","ans":"global","topenv":"R_GlobalEnv","fn":"eval","node":"nevermind","arch":"x86_64","os_name":"Linux","os_release":"6.12.101+deb13-amd64","os_version":"#1 SMP PREEMPT_DYNAMIC Debian 6.12.101-1 (2026-08-05)","pid":884692,"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 [2026-08-24 14:00:25] hi
    flog.info("hi %s", 84 / 2)
    #> INFO [2026-08-24 14:00:25] hi 42
    flog.info(paste("hi", 84 / 2))
    #> INFO [2026-08-24 14:00:25] hi 42
    flog.info(glue::glue("hi {84/2}"))
    #> INFO [2026-08-24 14:00:25] hi 42

logger

    log_info("hi")
    #> INFO [2026-08-24 14:00:25] hi
    log_info("hi {84/2}")
    #> INFO [2026-08-24 14:00:25] hi 42
    log_formatter(formatter_sprintf)
    log_info("hi %s", 84 / 2)
    #> INFO [2026-08-24 14:00:25] hi 42
    log_formatter(formatter_paste)
    log_info("hi", 84 / 2)
    #> INFO [2026-08-24 14:00:25] 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 ...")
    #> INFO [2026-08-24 14:00:25] Hello from logger in a futile.logger theme ...
    flog.warn("... where the default log message formatter is %s", "sprintf")
    #> WARN [2026-08-24 14:00:25] ... where the default log message formatter is 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")
    #> 2026-08-24 14:00:25.939153 INFO::hi there
    logwarn("watch out")
    #> 2026-08-24 14:00:25.950011 WARNING::watch out

logger

    log_info("hi there")
    #> INFO [2026-08-24 14:00:25] hi there
    log_warn("watch out")
    #> WARN [2026-08-24 14:00:25] 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(rev(logger:::log_levels_supported), envir = asNamespace("logger"))
    str(levels, give.attr = FALSE)
    #> List of 8
    #>  $ TRACE  : 'loglevel' int 600
    #>  $ DEBUG  : 'loglevel' int 500
    #>  $ INFO   : 'loglevel' int 400
    #>  $ SUCCESS: 'loglevel' int 350
    #>  $ WARN   : 'loglevel' int 300
    #>  $ ERROR  : 'loglevel' int 200
    #>  $ FATAL  : 'loglevel' int 100
    #>  $ OFF    : 'loglevel' int 0

### 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: 0x5598c5687758>
    #> <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")
    #> 2026-08-24 14:00:26.138171 INFO::hi
    loginfo("hi %s", 84 / 2)
    #> 2026-08-24 14:00:26.139388 INFO::hi 42
    loginfo(paste("hi", 84 / 2))
    #> 2026-08-24 14:00:26.140442 INFO::hi 42
    loginfo(glue::glue("hi {84/2}"))
    #> 2026-08-24 14:00:26.141658 INFO::hi 42

logger

    log_info("hi")
    #> INFO [2026-08-24 14:00:26] hi
    log_info("hi {84/2}")
    #> INFO [2026-08-24 14:00:26] hi {84/2}
    log_formatter(formatter_sprintf)
    log_info("hi %s", 84 / 2)
    #> INFO [2026-08-24 14:00:26] hi 42
    log_formatter(formatter_paste)
    log_info("hi", 84 / 2)
    #> INFO [2026-08-24 14:00:26] hi 42

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")
    #> INFO [2026-08-24 14:00:26] 42
    log_info(42)
    #> INFO [2026-08-24 14:00:26] 42: 42
    log_info(4 + 2)
    #> INFO [2026-08-24 14:00:26] 4 + 2: 6
    log_info("foo %s", "bar")
    #> INFO [2026-08-24 14:00:26] foo bar
    log_info(12, 1 + 1, 2 * 2)
    #> INFO [2026-08-24 14:00:26] 12: 12
    #> INFO [2026-08-24 14:00:26] 1 + 1: 2
    #> INFO [2026-08-24 14:00:26] 2 * 2: 4

### 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 ...")
    #> 2026-08-24 14:00:26 INFO::Hello from logger in a logging theme ...
    logwarn("... where the default log message formatter is %s", "sprintf", namespace = "foobar")
    #> 2026-08-24 14:00:26 WARN:foobar:... where the default log message formatter is sprintf

    ## reset the logger to the default state
    detach("package:logging", unload = TRUE)
    logger:::namespaces_reset()
    log_appender(appender_stdout)

## 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:logger.tester':
    #>
    #>     log_info
    #> The following objects are masked from 'package:logger':
    #>
    #>     as.loglevel, log_debug, log_error, log_fatal, log_info, log_warn,
    #>     logger
    #> The following object is masked from 'package:base':
    #>
    #>     debug
    log4r_logger <- log4r::logger()

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` used to have lowercase functions names referring to the log level, which takes a logging object and the log message, but since version 0.5, `log4r` also follows the `log_` prefix format:

log4r

    log4r::log_info(log4r_logger, "hi there")
    #> INFO  [2026-08-24 14:00:26] hi there
    log4r::log_warn(log4r_logger, "watch out")
    #> WARN  [2026-08-24 14:00:26] watch out

logger

    logger::log_info("hi there")
    #> INFO [2026-08-24 14:00:26] hi there
    logger::log_warn("watch out")
    #> WARN [2026-08-24 14:00:26] 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.

### Log record layout, formatter functions, and log record destinations

Since version 0.5, the `log4r` package also follows the `appenders` and layout formatter functions, similar to `logger`.

### Hierarchical logging and performance

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

## loggit and loggr

Both `loggit` and `loggr` provides an easy way of capturing `message`, `warning` and `stop` function messages. Similar functionality has been added to `logger` in the form of `log_message`, `log_warning` and `log_error` functions.