{"id":13566474,"url":"https://github.com/Log4s/log4s","last_synced_at":"2025-04-04T00:30:53.254Z","repository":{"id":8272716,"uuid":"9727224","full_name":"Log4s/log4s","owner":"Log4s","description":"High-performance SLF4J wrapper for Scala.","archived":false,"fork":false,"pushed_at":"2024-02-11T15:48:26.000Z","size":431,"stargazers_count":171,"open_issues_count":9,"forks_count":25,"subscribers_count":9,"default_branch":"master","last_synced_at":"2024-11-04T20:42:32.102Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"cbergmiller/bootstrap-formform","license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Log4s.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-04-28T07:57:48.000Z","updated_at":"2024-06-18T17:17:00.000Z","dependencies_parsed_at":"2024-08-01T13:23:37.094Z","dependency_job_id":"3990467f-5bdb-48bd-9ce6-599806e026b8","html_url":"https://github.com/Log4s/log4s","commit_stats":{"total_commits":368,"total_committers":14,"mean_commits":"26.285714285714285","dds":"0.20108695652173914","last_synced_commit":"690273d960850016382360017c19255eb7c9aded"},"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Log4s%2Flog4s","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Log4s%2Flog4s/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Log4s%2Flog4s/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Log4s%2Flog4s/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Log4s","download_url":"https://codeload.github.com/Log4s/log4s/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247103306,"owners_count":20884023,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T13:02:10.442Z","updated_at":"2025-04-04T00:30:48.245Z","avatar_url":"https://github.com/Log4s.png","language":"Scala","readme":"# Log4s\n\n**Note:** Log4s is not vulnerable to [log4shell (CVE-2021-44228)](https://nvd.nist.gov/vuln/detail/CVE-2021-44228) but the loggers that sit behind it may be. [See more details.](#cve-2021-44228-log4shell)\n\n**Note:** version 1.6 and above have *experimental* support for\n[Scala.js](https://www.scala-js.org/). See the table of contents below for\ndocumentation.\n\nTo get started quickly, you can add this dependency to your `build.sbt`\n\n    libraryDependencies += \"org.log4s\" %% \"log4s\" % \"1.10.0\"\n\n## Topics\n\n- [Introduction](#introduction)\n- [Requirements](#requirements)\n- [Using Log4s](#using-log4s)\n  - [Getting a logger](#getting-a-logger)\n  - [Performing logging](#logging-messages)\n  - [Exception logging](#exception-logging)\n  - [Diagnostic contexts](#diagnostic-contexts)\n- [Scala.js support](#scalajs-support)\n- [Log4s Testing](#log4s-testing)\n- [Unsupported features](#unsupported-features)\n- [Contributors](#contributors)\n\n## Introduction\n\nLogging is a generally solved problem on the JVM, thanks largely to the\nexcellent work of Ceki Gülcü and many others. The [SLF4J](http://slf4j.org)\nlibrary solves the problem of abstracting logging over different frameworks\non the JVM, and frameworks like [Logback](https://logback.qos.ch/) and\n[Log4j 2](https://logging.apache.org/log4j/2.x/) are both flexible and\npowerful.\n\nOn the JVM, Log4s simply sits on top of these existing subsystems. Scala's\nmacro and value classes, enable Log4s provide an idiomatic Scala façade that\ndoes not impose runtime overhead and that frequently outperforms the common\nusage patterns of the JVM APIs.\n\nLog4s also provides some additional functionality to improve the ease of\nlogging-related development, including the Log4s Testing framework for\nfacilitating the testing of logging-related code.\n\n## Using Log4s\n\n### Requirements\n\nScala 2.11, 2.12 and 2.13 are fully supported. No special settings or\ncompiler options are required: just add the dependency as described\nabove.\n\n#### Scala 2.10\n\nScala 2.10 support is still present, but it is beyond its support window: it\nmay be removed in any future minor release if there's a reason. (It will not\nbe removed in a patch release.)\n\nThe macro paradise compiler extensions are not required for Scala 2.10.\n\n### Getting a logger\n\nMost of the time, you simply want to define a logger with a name that matches\nthe enclosing class or module.  Log4s makes this common case as easy as\npossible, transparently deriving the name for you with zero overhead at runtime.\n\n```scala\npackage com.example.project\nimport org.log4s._\n\nclass DemoClass {\n  // Retrieves a logger for \"com.example.project.DemoClass\"\n  private[this] val logger = getLogger\n  ???\n}\n```\n\nThere is no requirement that you mark your loggers with `private[this]`, but\nthe compiler may bypass accessors and generate direct field access if you do.\n\nIt is not required that you import `org.log4s._` into your classes: calling\n`org.log4s.getLogger` will also have the same effect. I generally recommend\nimporting the entire `log4s` package: it doesn't have many symbols that are\nlikely to conflict, and importing the package makes it easy to access other\nlogging features if needed.\n\nAutomatic logger naming also works for modules (a.k.a. objects or\nsingletons).\n\n```scala\nobject DemoClass {\n  // Will log against category \"com.example.project.DemoClass\"\n  private val logger = org.log4s.getLogger\n  ???\n}\n```\n\nNotice that by default, Log4s does not include a _$_ at the end of logger\ncategories for modules.  This is slightly different behavior from the common\nidiom `LoggerFactory.getLogger(getClass)` used to get a logger for a module,\nbut this behavior is more consistent with Java practices and I suspect is what\na majority of users will prefer.  Future enhancements may provide a mechanism\nto allow the user to choose whether to include the trailing _$_.\n\n#### Custom Logger Names\n\nThere are situations where you may want to use a custom logger name. E.g., you\nmay want to have a special category for some kind of high-level events, or you\nmay want to consolidate the logging of two related classes.\n\nTo accomplish this, you can simply pass a name directly to `getLogger`.\n\n```scala\nimport org.log4s._\n\nobject CustomNamed {\n  private[this] val queryLogger = getLogger(\"queries\")\n}\n```\n\nAlthough this is fully supported, I recommend that you use the automatic\nclass-named loggers most of the time. Class-named loggers provide useful\ndebugging information and usually align well with the decisions you'll make\nabout which logging statements you want to enable in which situations. By\nletting the compiler provide the name for you, you also reduce the chance of\nerrors as you refactor your code.\n\n#### Instance or static?\n\nMy recommendation is that by default you create your loggers as instance\nvariables and mark them as `private[this]`.  This may be more compatible with\nsome complex classloading environments, and this practice is more friendly to\nprinciples of encapsulation.\n\nHowever, if a specific class will be instantiated very frequently, you may\nwant to move its logger to the companion module and mark it `private`.\nThere are some cases where greater visibility is justified, but these are\ninfrequent for most applications.\n\nThe SLF4J FAQ has a good discussion of the [tradeoffs between static and\ninstance loggers](http://slf4j.org/faq.html#declared_static).\n\n### Logging messages\n\nThe logger interfaces are extremely simple, but they're more powerful than\nthey look.  All the standard loggers take a single argument of type string.\n\n```scala\nclass MyClass(val data: Map[String,Int]) {\n  private[this] val logger = org.log4s.getLogger\n\n  logger.debug(\"Constructing new instance of MyClass\")\n  logger.trace(s\"New instance's data set: $data\")\n}\n```\n\nUnlike SLF4J, there are no special methods for parameterized logging, because\nit turns out to be completely unnecessary.  Parameterized logging serves two\nprimary purposes: it provides an easy way to construct complex strings, and it\nhelps avoid some of the costs of building a dynamic message when the message\nis at a level that is not enabled.\n\nAs you can see from the example, Scala 2.10's\n[string interpolation](http://docs.scala-lang.org/overviews/core/string-interpolation.html)\nis a much more powerful solution to the first of these issues—and it even saves\nthe runtime work of parsing a format string by splitting up the string into\neasily concatenated pieces at compile time.\n\nLog4s goes even further in that it uses macros to manipulate the execution so\nthat the string interpolations are not even performed unless the logger is\nenabled. It does this by inspecting the structure of the argument that you pass\ninto the logger.\n\nIf you pass a constant string literal, Log4s will make a direct, in-line call\nto the underlying SLF4J log method.  If you pass in any kind of more complex\nexpression, Log4s will wrap it in an \u003ctt\u003eis\u003ci\u003eLevel\u003c/i\u003eEnabled\u003c/tt\u003e call\nautomatically.  This is what SLF4J does when you use parameterized logging, but\nLog4s does it transparently and can even auto-wrap additional calculation.\n\nCompare the following:\n\n```java\nclass JavaClass {\n    ...\n    logger.trace(\"Element 1000: {}\", linkedList.get(1000));\n    ...\n}\n```\n\n```scala\nclass ScalaClass {\n  ...\n  logger.trace(s\"Element 1000: ${linkedList(1000)}\")\n  ...\n}\n```\n\nIn the Java API, parameterized logging is not enough: unless you wrap the call\nwith `isTraceEnabled`, you will still incur the cost of stepping through the\nlinked list to find element 1000 even if trace-level logging is disabled.\nWithout manual intervention, SLF4J only avoids the cost of string\nconcatenations.\n\nHowever, Log4s can do better. Its macros discover at compile time that you are\nconstructing a dynamic log statement and automatically wrap the entire\ncalculation with `isTraceEnabled`.\n\nThe string interpolation syntax is not required for this detection, but it\nis usually the easiest and best-performing approach.\n\nYou can also use message nesting with entire code blocks. If logging is not\nenabled at the provided level, the block is skipped entirely.\n\n```scala\nclass ComplexTrace {\n  ...\n  logger trace {\n    def helper(s: String) = ???\n    val x = ...\n    val y = helper(...)\n    s\"Combined trace message for $x: $y\"\n  }\n}\n```\n\nYou can, of course, accomplish the same thing using\n`if (logger.isTraceEnabled) ...`. If the logger is disabled, they will have\nidentical performance. However, the explicit test may perform slightly better\nthan a block in the case where the logger is enabled as a closure may be\nrequired to compile the block. (In most situations, these differences are\n_completely_ negligible, but designing for zero overhead and documenting any\nusage patterns that do add overhead is a major goal of Log4s.)\n\n### Exception logging\n\nWhen logging an exception, it's always the best practice to send the actual\nexception object into your logging system. This gives you flexibility in how\nit's displayed, the ability to do filtering, and additional options for things\nlike database logging.\n\nLog4s allows you to pass exceptions into your logger, while still maintining\nthe simple string-interpolation style API that makes it so convenient. To log\nan exception, use the following syntax.\n\n```scala\ntry {\n  ...\n} catch {\n  case e: Exception =\u003e logger.error(e)(\"Some error message\")\n}\n```\n\nThere is no method to log an error message without any message, because this\nis generally not a good practice. You can always feed it an empty string if\nyou really want. It's usually not desirable to use the exception's message, as\nmost logging systems will output this anyway.\n\nLike regular message logging, dynamic arguments are only evaluated if the\nprovided logger is turned on. This includes both the `Throwable` and the\nmessage itself.\n\nThis means you could use the following pattern to see who is calling a method,\nand if you were to disable trace logging you would only pay for the call to\n`isTraceEnabled`, which has a cost of only a few nanoseconds (according to the\n[SLF4J FAQ](http://www.slf4j.org/faq.html#trace)).\n\n```scala\nobject MyObject {\n  def xyz() {\n    logger.trace(new RuntimeException())(\"Got call into xyz\")\n    ???\n  }\n}\n```\n\n(This is more an illustration of the possibilities of dynamic message\nprocessing than a suggestion that this is the best way to get caller\ninformation. However, sometimes a low-tech solution like this can be a good\ncomplement to more complex solutions like profilers and debuggers.)\n\n### Diagnostic contexts\n\nMapped diagnostic contexts (MDCs) are a great way to put share common\ncontextual information across all log statements. Frameworks like Logback\nhave the ability to not just output them, but also use them in filtering to\nselect the type of logging to perform or even persist certain information in\ndatabases.\n\nMDCs in Log4s have the same semantics as those of standard MDCs in SLF4J.\nIn keeping with the design goal of making SLF4J idiomatic to Scala,\nLog4s's MDCs implement the standard interface for a\n[Scala mutable map](http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.Map).\n\n**Though I cover the map-style API first, see the [MDC convenience and\nsafety](#mdc-convenience-and-safety) section below for the simpler idiom that\nI recommend for most situations.**\n\n#### MDC Map-style API\n\nThe direct way to manipulate MDCs is through the `org.log4s.MDC` object.\n\n```scala\nimport org.log4s._\n\nobject DiagnosticExample {\n  private[this] val logger = getLogger\n\n  def doRequest(user: String) {\n    val requestId = java.util.UUID.randomUUID\n\n    // Empty out the MDC for this thread\n    MDC.clear\n\n    /* *************************** */\n    /* Set some context in the MDC */\n    /* *************************** */\n\n    // Set a single value\n    MDC(\"request-id\") = requestId.toString\n    // Set multiple values\n    MDC += (\"request-user\" -\u003e user, \"request-time\" -\u003e System.currentTimeMillis)\n\n    // Note that Log4s requires the caller to do string conversion. This helps\n    // ensure that it's really the implementation that you want.\n\n    /* *************** */\n    /* Use our context */\n    /* *************** */\n\n    // No need to put the request ID in the message: it's in the context\n    logger.debug(\"Processing request\")\n\n    /* ************************ */\n    /* Remove context variables */\n    /* ************************ */\n\n    // Remove a single value\n    MDC -= \"request-id\"\n    // Remove multiple values\n    MDC -= (\"request-user\", \"request-time\")\n  }\n}\n```\n\nThese are a few common examples, but all the mutator methods of a mutable\nmap will work. It's also possible to intermix calls to SLF4J's MDC methods\ndirectly: the Log4s map is backed by the actual SLF4J MDC.\n\n#### MDC convenience and safety\n\nNote that the example above has a common bug: if some exception happens during\nrequest processing, the MDC will not get cleaned up and it will leak to other\noperations. Because of this common situation, there's a convenience method\nthat does cleanup in a finalizer block. I recommend using this approach for\nmost common settings.\n\n```scala\nimport org.log4s._\n\nobject BlockExample {\n  def doRequest(user: String) {\n    val requestId = java.util.UUID.randomUUID\n\n    // This context operates only for the block, then cleans itself up\n    MDC.withCtx(\"request-id\" -\u003e requestId.toString, \"request-user\" -\u003e user) {\n      logger.debug(\"Processing request\")\n    }\n  }\n}\n```\n\nNesting context blocks is permitted. The inner context block retains the\nvalues of the outer context. If there are conflicts, the inner block wins, but\nthe outer value is restored when the inner block is completed.\n\nThis ability to restore previous values on block exit does require their\nstorage in a map which adds slight memory overhead. If you are in a tight loop\nwith nested contexts, you may have better performance if you add and remove\nvalues directly. These performance costs apply only to the block-based API,\nnot the map-style API.\n\n## Scala.js Support\n\n**Scala.js support is currently experimental.** It should be stable enough to\nuse reliably, but there may be API changes in the future. If there are changes,\nthey would likely be to either the configuration system or to the JavaScript native APIs.\n\nMany Scala.js-specific APIs are in the `org.log4s.log4sjs` package. It is not\ncurrently recommended that you import this full package. There may be many public\nAPIs in here that become private later.\n\n### Scala-defined usage\n\nYour Scala code that targets JavaScript can retrieve and use loggers *exactly* the same way\nthat you would when targeting the JVM, fulfilling the basic promise of Scala.js.\n\n#### Configuration\n\nUnlike when targeting the JVM, standard frameworks like Logback or Log4j are\nnot available to do the configuration of the logging system. Instead, there is\nan API that you can call to adjust logging thresholds and appenders.\n\nNormally, you will call this API very early on during your application's\nstartup to set up your logging configuration. However, you can adjust the\nsettings at any time.\n\n```scala\nimport org.log4s._\n\ndef initLogging(): Unit = {\n  import Log4sConfig._\n\n  /* Set `org.log4s.foo` and any children to log only Info or higher */\n  setLoggerThreshold(\"org.log4s.foo\", Info)\n\n  /* Set `org.log4s` to not log anything. This will not override the specific\n   * setting we already applied to `org.log4s.foo`. */\n  setLoggerThreshold(\"org.log4s\", OffThreshold)\n\n  /* Set to log everything */\n  setLoggerThreshold(\"\", AllThreshold)\n\n  /* Unset a previously customized threshold. *Now* this category will inherit\n   * from the parent level, which we disabled. */\n  resetLoggerThreshold(\"org.log4s.foo\")\n\n  /* Add a custom appender */\n  val myAppender = { ev: log4sjs.LoggedEvent =\u003e ??? }\n  /* Add a custom appender, leaving others in place */\n  addLoggerAppender(\"org.example\", myAppender)\n\n  /* Set the specific appenders. The `additive` parameter controls whether\n   * this is in addition to the appenders of the parent logger. The `false`\n   * here means to *not* include any parent appenders. */\n  setLoggerAppenders(\"org.log4s.audit\", false, Seq(myAppender))\n\n  /* The `true` here means that myAppender` from the `audit` logger will still\n   * be called since it allows additive inheritance. */\n  val appender2 = { ev: log4sjs.LoggedEvent =\u003e ??? }\n  setLoggerAppenders(\"org.log4s.audit.detailed\", true, Seq(appender2))\n\n  /* Resets the logger to default settings. This also adjusts the\n   * `org.log4s.audit.detailed` appenders, since that logger inherits\n   * appenders from its parents */\n  resetLoggerAppenders(\"org.log4s.audit\")\n}\n```\n\n### JavaScript direct usage\n\nTo JavaScript, Log4s exposes a few key methods as a module so that you can\naccess logging facilities from any JavaScript code you might have. You should\nconsult the Scala.js documentation for how to get access to your modules from\nJavaScript.\n\nNote that all the examples below will assume you have already imported the\nmodule under the name `log4s`, which could look like this for a separately\npackaged log4s.\n\n```javascript\nvar log4s = require('log4s-opt.js')\n```\n\n#### Basic logging\n\n`getLogger` is a top-level function that takes a String and gives you back a\nlogger object, just as you'd expect in Scala.\n\nThe methods on a logger are straightforward:\n\n```javascript\nvar logger = log4s.getLogger(\"org.log4s\")\n\nlogger.debug(\"testing\")\n\nif (logger.isWarnEnabled) {\n  logger.warn(\"Something went wrong\", new Error())\n}\n```\n\nThe names of the log levels are the same as in Scala. Note that doing\ntrace-level logging does not trigger the JavaScript's console trace, which\nautomatically dumps a stack trace. if you want this behavior, you can always\nadd a custom appender that inspects the level. If you have advanced needs in\nthis area, please file a feature request.\n\n#### MDCs in JavaScript\n\nThe MDC is available through JavaScript just as it is in Scala. Here's an example\n\n```javascript\n/* Clear the MDC before we start */\nlog4s.MDC.clear()\n\nlog4s.MDC.put(\"user\", \"john.doe\")\n/* Do some logging */\nlog4s.MDC.remove(\"user\")\n/* Fetch an MDC value (usually not recommended) */\nlog4s.MDC.get(\"user\")\n/* Or get a copy of the entire MDC (also not usually recommended) */\nlog4s.MDC.getCopyOfContextMap()\n```\n\nJust as in Scala, there's a \"with context\" method that automatically handles\nany cleanup for you. It's not quite as convenient in JavaScript as in Scala,\nbut it can still be a good way to ensure your MDC gets cleaned up. These are\ncurried functions: you pass it a context and then it gives you new function to\nwhich you pass a zero-argument function that does the work.\n\n```javascript\nvar logger = log4s.getLogger(\"com.example\")\n/* With a single MDC value */\nlog4s.MDC.withCtx(\"user\", \"jane.roe\")(() =\u003e {\n  /* Do some stuff */\n  logger.debug(\"User did something\")\n})\n/* With several MDC values */\nlog4s.MDC.withCtx({\"user\": \"benway\", \"query\": \"1234\"})(() =\u003e {\n  /* Do some stuff */\n  logger.debug(\"Use with complext context did something\")\n})\n```\n\n#### JavaScript Configuration Objects\n\nThe same basic methods that you would use to do Scala-defined configuration\nare available through JavaScript. See the documentation above for details on\nhow to use them.\n\n```javascript\nlog4s.Config.setLoggerThreshold(\"org.log4s\", log4s.Info)\nlog4s.Config.resetLoggerThreshold(\"org.log4s\")\nlog4s.Config.addLoggerAppender(\"org\", e =\u003e console.log(e.level.name + \": \" + e.message))\nlog4s.Config.setLoggerAppenders(\"org.test\", false, [e =\u003e console.log(e.message)])\nlog4s.Config.resetLoggerAppenders(\"org\")\n```\n\nAppenders can be created by passing in a JavaScript function.\n\nFor parameters, there are top-level threshold/level objects available:\n\n- `AllThreshold`\n- `Trace`\n- `Debug`\n- `Info`\n- `Warn`\n- `Error`\n- `OffThreshold`\n\n## Log4s-Testing\n\nThere is a Logback-specific testing library that allows you to do mock-object\nstyle testing of your log messages if you'd like. This was built for internal\ntesting of Log4s, but it has been made public by request.\n\n### Setup\n\nThis only works if you are using Logback as your logging framework, at\nleast during testing. (Doing this will not interfere with using a different\nframework for your runtime logging if you correctly configure the two\nclasspaths.)\n\n#### SBT config\n\n    libraryDependencies += \"org.log4s\" %% \"log4s-testing\" % log4sVersion % \"test\"\n\nI recommend you use a `val log4sVersion` to match the version number with the\nmain Log4s dependency.\n\n#### Logback config\n\nYou'll then want to add lines like the following in your `logback-test.xml`\n\n```xml\n\u003cappender name=\"TEST\" class=\"org.log4s.TestAppender\"/\u003e\n\u003clogger name=\"org.log4s.abc\" additivity=\"false\" level=\"TRACE\"\u003e\n  \u003c!-- Set additivity to `false` if you don't want this logging to actually go to the main output. --\u003e\n  \u003cappender-ref ref=\"TEST\" /\u003e\n\u003c/logger\u003e\n```\n\nFull documentation of this is beyond the scope of this document, but you need\nto create the custom appender and register it with the appropriate categories.\nNote that Logback's Groovy-based configuration is more convenient and flexible\nthan the XML for more complicated logging configurations, but it's less\nfamiliar and adds an extra runtime dependency on Groovy.\n\n\n### Usage\n\nThe steps are relatively simple\n\n1. Get access to a logger that hooked up to your appender\n1. Write one or more events to that logger\n1. Call into the TestAppender object to dequeue the events and inspect them\n\nHere's a simple example of code you might write:\n\n```scala\nimport org.log4s._\nimport org.scalatest._\n\n/* An auto-named logger would work just as well as long as that logger\n * is hooked up in your Logback configuration. */\nval testLogger = getLogger(\"org.log4s.abc\")\n\nTestAppender.withAppender() {\n  testLogger.debug(\"Here's a test message\")\n  val eventOpt = TestAppender.dequeue\n  eventOpt should be ('defined)\n  eventOpt foreach { e =\u003e\n    e.message should equal (\"Here's a test message\")\n    e.throwable should not be 'defined\n  }\n}\n```\n\nMore examples are available if you look through the various test classes\nin this project.\n\n### Should I test my logging?\n\nTesting scope and philosophy is a complex topic far beyond the reach of this\ndocuemnt, but I can give some general guidance based on my personal views.\n\nGood tests validate the behavior that callers or users should expect when\ninteracting with your code, but not arbitrary implementation details. Quality\ntests don't fail just because you changed something—unless that thing impacts\nthe expected behavior of the code. My view is effectively that tests should\nattempt to fully verify the black-box behavior of a piece of code while\nignoring details that do not affect black-box behavior. (However, white-box\ndevelopment techniques may be useful to provide this verification, and\njudgment is required in the drawing of the lines.)\n\nI'll give two examples.\n\nAt one extreme is a trace statement that is used for developer debugging. You\nprobably would not benefit from crying wolf with a test failure just because\nyou added an additional bit of detail to a debug statement or you changed its\npunctuation. In my opinion, this log statement is probably not part of the\ncode's specification and should not be tested.\n\nAt the other extreme would be a scenario where you're using your logging\nframework to generate audit logs that track access to secured resources. This\nis important functionality that may be required for regulatory compliance. It\nis _strongly_ advisable to develop tests that ensure this log is written as\nexpected. I would probably consider a white-box unit test to ensure the messages\nwent to the right logger in the right format and a black-box test that inspected\nthe actual output log files to ensure events were being written in an end-to-end\nmanner.\n\nMost real situations will lie between these two extremes, and you will need to\nuse judgment. In my estimation, most applications probably do not need or want\ntests that verify the details of their logging, but there are many situations\nwhere this testing is approrpiate.\n\n## Unsupported features\n\nThe following potential or suggested features are not implemented. If some\nmissing feature is particularly valuable to you, feel free to reach out with\nyour requests or suggestions. I'm also—of course—open to pull requests,\nbut please drop me an email first if there are significant new APIs or\nfeatures so we can agree on the general design.\n\n- A `scalac` compiler flag or environment variable to automatically disable\n  all logging below a certain level.\n- Marker support.\n\n## CVE-2021-44228 (\"log4shell\")\n\nlog4s delegates all logging operations to [slf4j][slf4j].  log4s is\nnot directly at risk from CVE-2021-44228, but your configured slf4j\nprovider may put you at risk.  See [slf4j's comments on\nCVE-2021-44228][slf4j-log4shell] for more.\n\n[slf4j]: https://www.slf4j.org/\n[slf4j-log4shell]: https://www.slf4j.org/log4shell.html\n\n## Contributors\n\n### Maintainers\n\n- [Sarah Gerweck](https://github.com/sarahgerweck/) (creator \u0026 primary maintainer)\n- [Ross A. Baker](https://github.com/rossabaker)\n\n### Additional contributors\n\nHere are all other contributors, listed chronologically. Thanks to all!\n\n- [Bryce Anderson](https://github.com/bryce-anderson)\n- [David Ross](https://github.com/dyross)\n- [Seth Tisue](https://github.com/SethTisue)\n- [Michal](https://github.com/mkows)\n- [Sean Sullivan](https://github.com/sullis)\n- [Olli Helenius](https://github.com/liff)\n- [Raúl Piaggio](https://github.com/rpiaggio)\n- [Sergey Torgashov](https://github.com/satorg)\n- [Kevin Lee](https://github.com/Kevin-Lee)\n- [Vasil Vasilev](https://github.com/vasilmkd)\n","funding_links":[],"categories":["Scala","Table of Contents"],"sub_categories":["Extensions"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLog4s%2Flog4s","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FLog4s%2Flog4s","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FLog4s%2Flog4s/lists"}