Repository: 7mind/izumi
Pull request #877 (2020-01-31 21:04:15)

working logger codec (arguments only)
Addresses  #507

codecov-io: # [Codecov](https://codecov.io/gh/7mind/izumi/pull/877?src=pr&el=h1) Report
> Merging [#877](https://codecov.io/gh/7mind/izumi/pull/877?src=pr&el=desc) into [develop](https://codecov.io/gh/7mind/izumi/commit/3ceefedb8a818a933f192180d9c3ae1f04c0c63c?src=pr&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `64.84%`.

[![Impacted file tree graph](https://codecov.io/gh/7mind/izumi/pull/877/graphs/tree.svg?width=650&token=77pa3UU1Je&height=150&src=pr)](https://codecov.io/gh/7mind/izumi/pull/877?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##           develop     #877      +/-   ##
===========================================
+ Coverage     70.1%   70.12%   +0.02%     
===========================================
  Files          561      565       +4     
  Lines        14251    14361     +110     
  Branches       743      748       +5     
===========================================
+ Hits          9990    10071      +81     
- Misses        4261     4290      +29
```


| [Impacted Files](https://codecov.io/gh/7mind/izumi/pull/877?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [.../logstage/macros/ArgumentNameExtractionMacro.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9sb2dzdGFnZS9tYWNyb3MvQXJndW1lbnROYW1lRXh0cmFjdGlvbk1hY3JvLnNjYWxh) | `80% <ø> (ø)` | :arrow_up: |
| [.../logstage-core/src/main/scala/logstage/LogIO.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtY29yZS9zcmMvbWFpbi9zY2FsYS9sb2dzdGFnZS9Mb2dJTy5zY2FsYQ==) | `25% <0%> (ø)` | :arrow_up: |
| [...e/src/main/scala/logstage/strict/LogIOStrict.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtY29yZS9zcmMvbWFpbi9zY2FsYS9sb2dzdGFnZS9zdHJpY3QvTG9nSU9TdHJpY3Quc2NhbGE=) | `0% <0%> (ø)` | :arrow_up: |
| [...e-core/src/main/scala/izumi/logstage/api/Log.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9sb2dzdGFnZS9hcGkvTG9nLnNjYWxh) | `65.85% <100%> (ø)` | :arrow_up: |
| [.../src/main/scala/logstage/circe/LogstageCirce.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtcmVuZGVyaW5nLWNpcmNlL3NyYy9tYWluL3NjYWxhL2xvZ3N0YWdlL2NpcmNlL0xvZ3N0YWdlQ2lyY2Uuc2NhbGE=) | `75% <100%> (+75%)` | :arrow_up: |
| [.../rendering/json/LogstageCirceRenderingPolicy.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtcmVuZGVyaW5nLWNpcmNlL3NyYy9tYWluL3NjYWxhL2l6dW1pL2xvZ3N0YWdlL2FwaS9yZW5kZXJpbmcvanNvbi9Mb2dzdGFnZUNpcmNlUmVuZGVyaW5nUG9saWN5LnNjYWxh) | `91.54% <100%> (+0.24%)` | :arrow_up: |
| [...-circe/src/main/scala/logstage/circe/package.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtcmVuZGVyaW5nLWNpcmNlL3NyYy9tYWluL3NjYWxhL2xvZ3N0YWdlL2NpcmNlL3BhY2thZ2Uuc2NhbGE=) | `100% <100%> (+100%)` | :arrow_up: |
| [...a/izumi/logstage/api/rendering/LogstageCodec.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9sb2dzdGFnZS9hcGkvcmVuZGVyaW5nL0xvZ3N0YWdlQ29kZWMuc2NhbGE=) | `39.28% <39.28%> (ø)` | |
| [...ala/izumi/logstage/api/strict/IzStrictLogger.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9sb2dzdGFnZS9hcGkvc3RyaWN0L0l6U3RyaWN0TG9nZ2VyLnNjYWxh) | `57.14% <50%> (+57.14%)` | :arrow_up: |
| [...e/src/main/scala/izumi/logstage/api/IzLogger.scala](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree#diff-bG9nc3RhZ2UvbG9nc3RhZ2UtY29yZS9zcmMvbWFpbi9zY2FsYS9penVtaS9sb2dzdGFnZS9hcGkvSXpMb2dnZXIuc2NhbGE=) | `57.14% <50%> (ø)` | :arrow_up: |
| ... and [11 more](https://codecov.io/gh/7mind/izumi/pull/877/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/7mind/izumi/pull/877?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/7mind/izumi/pull/877?src=pr&el=footer). Last update [3ceefed...83700c1](https://codecov.io/gh/7mind/izumi/pull/877?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/IzLogger.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/IzLogger.scala
index e4f55171f..20de4d92e 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/IzLogger.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/IzLogger.scala
@@ -2,6 +2,7 @@ package izumi.logstage.api
 
 import izumi.logstage.api.Log.CustomContext
 import izumi.logstage.api.logger.{AbstractMacroLogger, LogRouter, RoutingLogger}
+import izumi.logstage.api.rendering.AnyEncoded
 
 class IzLogger
 (
@@ -10,12 +11,12 @@ class IzLogger
 ) extends RoutingLogger with AbstractMacroLogger {
 
   override def withCustomContext(context: CustomContext): IzLogger = new IzLogger(router, customContext + context)
-  final def withCustomContext(context: (String, Any)*): IzLogger = withCustomContext(context.toMap)
-  final def withCustomContext(context: Map[String, Any]): IzLogger = withCustomContext(CustomContext(context))
+  final def withCustomContext(context: (String, AnyEncoded)*): IzLogger = withCustomContext(context.toMap)
+  final def withCustomContext(context: Map[String, AnyEncoded]): IzLogger = withCustomContext(CustomContext(context))
 
   final def apply(context: CustomContext): IzLogger = withCustomContext(context)
-  final def apply(context: (String, Any)*): IzLogger = withCustomContext(context.toMap)
-  final def apply(context: Map[String, Any]): IzLogger = withCustomContext(context)
+  final def apply(context: (String, AnyEncoded)*): IzLogger = withCustomContext(context.toMap)
+  final def apply(context: Map[String, AnyEncoded]): IzLogger = withCustomContext(context)
 }
 
 object IzLogger extends IzLoggerConvenienceApi {
@@ -25,3 +26,7 @@ object IzLogger extends IzLoggerConvenienceApi {
     new IzLogger(r, context)
   }
 }
+
+
+
+
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/Log.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/Log.scala
index 8b9fdcb4a..e5b63a221 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/Log.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/Log.scala
@@ -2,7 +2,7 @@ package izumi.logstage.api
 
 import izumi.fundamentals.collections.IzCollections._
 import izumi.fundamentals.platform.language.{CodePosition, CodePositionMaterializer, SourceFilePosition}
-import izumi.logstage.api.rendering.LogstageCodec
+import izumi.logstage.api.rendering.{AnyEncoded, LogstageCodec}
 import izumi.logstage.macros.{LogMessageMacro, LogMessageMacroStrict}
 
 import scala.language.experimental.macros
@@ -82,15 +82,15 @@ object Log {
   }
 
   object CustomContext {
-    def apply(map: Map[String, Any]): CustomContext = {
+    def apply(map: Map[String, AnyEncoded]): CustomContext = {
       val logArgs = map.map {
-        case (k, v) => LogArg(Seq(k), v, hiddenName = false, None)
+        case (k, v) => LogArg(Seq(k), v.value, hiddenName = false, v.codec)
       }.toList
 
       CustomContext(logArgs)
     }
 
-    def apply(args: (String, Any)*)(implicit dummy: DummyImplicit): CustomContext = {
+    def apply(args: (String, AnyEncoded)*)(implicit dummy: DummyImplicit): CustomContext = {
       CustomContext(Map(args: _*))
     }
 
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/AnyEncoded.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/AnyEncoded.scala
new file mode 100644
index 000000000..59e9b57e5
--- /dev/null
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/AnyEncoded.scala
@@ -0,0 +1,39 @@
+package izumi.logstage.api.rendering
+
+import scala.language.implicitConversions
+
+sealed trait AnyEncoded {
+  type T
+  def value: T
+  def codec: Option[LogstageCodec[T]]
+}
+
+object AnyEncoded extends AnyEncodedLow {
+
+  @inline implicit def toPair[A](a: (String, A))(implicit maybeCodec: LogstageCodec[A] = null): (String, AnyEncoded) = (a._1, to(a._2))
+
+}
+
+
+sealed trait StrictEncoded extends AnyEncoded
+
+trait StrictEncodedLow {
+  @inline implicit def to[A](a: A)(implicit maybeCodec: LogstageCodec[A]): StrictEncoded = new StrictEncoded {
+    override type T = A
+
+    override def value: A = a
+
+    override def codec: Option[LogstageCodec[A]] = Option(maybeCodec)
+  }
+
+}
+
+object StrictEncoded extends StrictEncodedLow {
+
+  @inline implicit def toPair[A](a: (String, A))(implicit maybeCodec: LogstageCodec[A]): (String, StrictEncoded) = (a._1, to(a._2))
+}
+
+trait AnyEncodedLow {
+  @inline implicit def to[A](a: A)(implicit maybeCodec: LogstageCodec[A] = null): AnyEncoded = StrictEncoded.to(a)
+
+}
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/LogstageCodec.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/LogstageCodec.scala
index 0cf298413..6f098e5f3 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/LogstageCodec.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/rendering/LogstageCodec.scala
@@ -1,13 +1,124 @@
 package izumi.logstage.api.rendering
 
-trait LogstageCodec[T] {
+import izumi.fundamentals.platform.language.unused
 
+trait LogstageScalarWriter {
+  def write(a: Byte): Unit
+  def write(a: Short): Unit
+  def write(a: Char): Unit
+  def write(a: Int): Unit
+  def write(a: Long): Unit
+  def write(a: Float): Unit
+  def write(a: Double): Unit
+  def write(a: Boolean): Unit
+  def write(a: String): Unit
+  def write(a: BigDecimal): Unit
+  def write(a: BigInt): Unit
 }
 
-object LogstageCodec {
+trait LogstageWriter extends LogstageScalarWriter {
+  def openList(): Unit
+  def closeList(): Unit
+  def openMap(): Unit
+  def closeMap(): Unit
+  def nextListElementOpen(): Unit
+  def nextListElementClose(): Unit
+  def nextMapElementOpen(): Unit
+  def nextMapElementClose(): Unit
+  def mapElementSplitter(): Unit
+
+  def writeNull(): Unit
+}
+
+trait LogstageCodec[-T] {
+  def write(value: T, writer: LogstageWriter)
+}
+
+trait LogstageCodecs {
+  implicit object LogstageCodecString extends LogstageCodec[String] {
+    override def write(value: String, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecBoolean extends LogstageCodec[Boolean] {
+    override def write(value: Boolean, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecByte extends LogstageCodec[Byte] {
+    override def write(value: Byte, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecShort extends LogstageCodec[Short] {
+    override def write(value: Short, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecChar extends LogstageCodec[Char] {
+    override def write(value: Char, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecInt extends LogstageCodec[Int] {
+    override def write(value: Int, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecLong extends LogstageCodec[Long] {
+    override def write(value: Long, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecFloat extends LogstageCodec[Float] {
+    override def write(value: Float, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecDouble extends LogstageCodec[Double] {
+    override def write(value: Double, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecBigDecimal extends LogstageCodec[BigDecimal] {
+    override def write(value: BigDecimal, writer: LogstageWriter): Unit = writer.write(value)
+  }
+
+  implicit object LogstageCodecBigInt extends LogstageCodec[BigInt] {
+    override def write(value: BigInt, writer: LogstageWriter): Unit = writer.write(value)
+  }
+}
+
+object LogstageCodec extends LogstageCodecs {
+
+  implicit def listCodec[T: LogstageCodec]: LogstageCodec[Iterable[T]] = new LogstageCodec[Iterable[T]] {
+    override def write(value: Iterable[T], writer: LogstageWriter): Unit = {
+      writer.openList()
+
+      value.foreach {
+        v =>
+          writer.nextListElementOpen()
+          implicitly[LogstageCodec[T]].write(v, writer)
+          writer.nextListElementClose()
+      }
+
+      writer.closeList()
+    }
+  }
+
+  implicit def mapCodec[K: LogstageCodec, V: LogstageCodec]: LogstageCodec[Map[K, V]] = new LogstageCodec[Map[K, V]] {
+
+    override def write(value: Map[K, V], writer: LogstageWriter): Unit = {
+      writer.openMap()
+      value.foreach {
+        case (k, v) =>
+          writer.nextMapElementOpen()
+          implicitly[LogstageCodec[K]].write(k, writer)
+          writer.mapElementSplitter()
+          implicitly[LogstageCodec[V]].write(v, writer)
+          writer.nextMapElementClose()
+      }
+      writer.closeMap()
+
+    }
+
+  }
+
+  implicit object LogstageCodecNull extends LogstageCodec[Null] {
+    override def write(@unused value: Null, writer: LogstageWriter): Unit = writer.writeNull()
+  }
+
 
-//  implicit object LogstageCodecString extends LogstageCodec[String] {
-//
-//  }
 
 }
\ No newline at end of file
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/api/strict/IzStrictLogger.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/api/strict/IzStrictLogger.scala
index c3c5b12fa..6a0341266 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/api/strict/IzStrictLogger.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/api/strict/IzStrictLogger.scala
@@ -2,6 +2,7 @@ package izumi.logstage.api.strict
 
 import izumi.logstage.api.Log.CustomContext
 import izumi.logstage.api.logger.{AbstractMacroStrictLogger, LogRouter, RoutingLogger}
+import izumi.logstage.api.rendering.StrictEncoded
 import izumi.logstage.api.{IzLogger, IzLoggerConvenienceApi, Log}
 
 
@@ -12,12 +13,12 @@ class IzStrictLogger
 ) extends RoutingLogger with AbstractMacroStrictLogger {
 
   override def withCustomContext(context: CustomContext): IzLogger = new IzLogger(router, customContext + context)
-  final def withCustomContext(context: (String, Any)*): IzLogger = withCustomContext(context.toMap)
-  final def withCustomContext(context: Map[String, Any]): IzLogger = withCustomContext(CustomContext(context))
+  final def withCustomContext(context: (String, StrictEncoded)*): IzLogger = withCustomContext(context.toMap)
+  final def withCustomContext(context: Map[String, StrictEncoded]): IzLogger = withCustomContext(CustomContext(context))
 
   final def apply(context: CustomContext): IzLogger = withCustomContext(context)
-  final def apply(context: (String, Any)*): IzLogger = withCustomContext(context.toMap)
-  final def apply(context: Map[String, Any]): IzLogger = withCustomContext(context)
+  final def apply(context: (String, StrictEncoded)*): IzLogger = withCustomContext(context.toMap)
+  final def apply(context: Map[String, StrictEncoded]): IzLogger = withCustomContext(context)
 
 }
 
diff --git a/logstage/logstage-core/src/main/scala/izumi/logstage/macros/ArgumentNameExtractionMacro.scala b/logstage/logstage-core/src/main/scala/izumi/logstage/macros/ArgumentNameExtractionMacro.scala
index 80c1761c1..ce1a97fe9 100644
--- a/logstage/logstage-core/src/main/scala/izumi/logstage/macros/ArgumentNameExtractionMacro.scala
+++ b/logstage/logstage-core/src/main/scala/izumi/logstage/macros/ArgumentNameExtractionMacro.scala
@@ -234,6 +234,7 @@ class ArgumentNameExtractionMacro[C <: blackbox.Context](final val c: C, strict:
 
     val tc = maybeCodec match {
       case Failure(exception) if strict =>
+        c.error(param.tree.pos, s"Implicit search failed for parameter ${c.universe.showCode(param.tree)}: ${param.tree.tpe}")
         throw exception
       case Failure(_) =>
         None
diff --git a/logstage/logstage-core/src/main/scala/logstage/LogIO.scala b/logstage/logstage-core/src/main/scala/logstage/LogIO.scala
index 1ef62f21e..fd1622e0a 100644
--- a/logstage/logstage-core/src/main/scala/logstage/LogIO.scala
+++ b/logstage/logstage-core/src/main/scala/logstage/LogIO.scala
@@ -4,6 +4,7 @@ import izumi.functional.mono.SyncSafe
 import izumi.fundamentals.platform.language.{CodePositionMaterializer, unused}
 import izumi.logstage.api.Log._
 import izumi.logstage.api.logger.{AbstractLogger, AbstractMacroLoggerF}
+import izumi.logstage.api.rendering.AnyEncoded
 import logstage.UnsafeLogIO.UnsafeLogIOSyncSafeInstance
 
 import scala.language.implicitConversions
@@ -13,11 +14,11 @@ trait LogIO[F[_]] extends UnsafeLogIO[F] with AbstractMacroLoggerF[F] {
   def log(logLevel: Level)(messageThunk: => Message)(implicit pos: CodePositionMaterializer): F[Unit]
   def withCustomContext(context: CustomContext): LogIO[F]
 
-  final def withCustomContext(context: (String, Any)*): LogIO[F] = withCustomContext(context.toMap)
-  final def withCustomContext(context: Map[String, Any]): LogIO[F] = withCustomContext(CustomContext(context))
+  final def withCustomContext(context: (String, AnyEncoded)*): LogIO[F] = withCustomContext(context.toMap)
+  final def withCustomContext(context: Map[String, AnyEncoded]): LogIO[F] = withCustomContext(CustomContext(context))
   final def apply(context: CustomContext): LogIO[F] = withCustomContext(context)
-  final def apply(context: (String, Any)*): LogIO[F] = withCustomContext(context.toMap)
-  final def apply(context: Map[String, Any]): LogIO[F] = withCustomContext(context)
+  final def apply(context: (String, AnyEncoded)*): LogIO[F] = withCustomContext(context.toMap)
+  final def apply(context: Map[String, AnyEncoded]): LogIO[F] = withCustomContext(context)
 }
 
 object LogIO {
diff --git a/logstage/logstage-core/src/main/scala/logstage/strict/LogIOStrict.scala b/logstage/logstage-core/src/main/scala/logstage/strict/LogIOStrict.scala
index a106f7b7e..95312aceb 100644
--- a/logstage/logstage-core/src/main/scala/logstage/strict/LogIOStrict.scala
+++ b/logstage/logstage-core/src/main/scala/logstage/strict/LogIOStrict.scala
@@ -4,6 +4,7 @@ import izumi.functional.mono.SyncSafe
 import izumi.fundamentals.platform.language.{CodePositionMaterializer, unused}
 import izumi.logstage.api.Log._
 import izumi.logstage.api.logger.{AbstractLogger, AbstractMacroLoggerF}
+import izumi.logstage.api.rendering.StrictEncoded
 import logstage.UnsafeLogIO.UnsafeLogIOSyncSafeInstance
 import logstage.{Level, LogBIO, UnsafeLogIO}
 
@@ -14,11 +15,11 @@ trait LogIOStrict[F[_]] extends UnsafeLogIO[F] with AbstractMacroLoggerF[F] {
   def log(logLevel: Level)(messageThunk: => Message)(implicit pos: CodePositionMaterializer): F[Unit]
   def withCustomContext(context: CustomContext): LogIOStrict[F]
 
-  final def withCustomContext(context: (String, Any)*): LogIOStrict[F] = withCustomContext(context.toMap)
-  final def withCustomContext(context: Map[String, Any]): LogIOStrict[F] = withCustomContext(CustomContext(context))
+  final def withCustomContext(context: (String, StrictEncoded)*): LogIOStrict[F] = withCustomContext(context.toMap)
+  final def withCustomContext(context: Map[String, StrictEncoded]): LogIOStrict[F] = withCustomContext(CustomContext(context))
   final def apply(context: CustomContext): LogIOStrict[F] = withCustomContext(context)
-  final def apply(context: (String, Any)*): LogIOStrict[F] = withCustomContext(context.toMap)
-  final def apply(context: Map[String, Any]): LogIOStrict[F] = withCustomContext(context)
+  final def apply(context: (String, StrictEncoded)*): LogIOStrict[F] = withCustomContext(context.toMap)
+  final def apply(context: Map[String, StrictEncoded]): LogIOStrict[F] = withCustomContext(context)
 }
 
 
diff --git a/logstage/logstage-rendering-circe/src/main/scala/izumi/logstage/api/rendering/json/LogstageCirceRenderingPolicy.scala b/logstage/logstage-rendering-circe/src/main/scala/izumi/logstage/api/rendering/json/LogstageCirceRenderingPolicy.scala
index 88b0fc617..54d72c593 100644
--- a/logstage/logstage-rendering-circe/src/main/scala/izumi/logstage/api/rendering/json/LogstageCirceRenderingPolicy.scala
+++ b/logstage/logstage-rendering-circe/src/main/scala/izumi/logstage/api/rendering/json/LogstageCirceRenderingPolicy.scala
@@ -6,11 +6,12 @@ import izumi.fundamentals.platform.exceptions.IzThrowable._
 import izumi.logstage.api.Log
 import izumi.logstage.api.Log.LogArg
 import izumi.logstage.api.rendering.logunits.LogFormat
-import izumi.logstage.api.rendering.{RenderedMessage, RenderedParameter, RenderingPolicy}
+import izumi.logstage.api.rendering.{LogstageCodec, RenderedMessage, RenderedParameter, RenderingPolicy}
 
 import scala.collection.mutable
 import scala.runtime.RichInt
 
+
 class LogstageCirceRenderingPolicy(prettyPrint: Boolean = false) extends RenderingPolicy {
 
   import LogstageCirceRenderingPolicy._
@@ -111,7 +112,13 @@ class LogstageCirceRenderingPolicy(prettyPrint: Boolean = false) extends Renderi
 
     val mapParameter = mapScalar orElse mapStruct
 
-    mapParameter(parameter.value)
+    parameter.arg.codec match {
+      case Some(codec) =>
+        LogstageCirceWriter.write(codec.asInstanceOf[LogstageCodec[Any]], parameter.value)
+
+      case None =>
+        mapParameter(parameter.value)
+    }
   }
 
   private val mapScalar: PartialFunction[Any, Json] = {
diff --git a/logstage/logstage-rendering-circe/src/main/scala/izumi/logstage/api/rendering/json/LogstageCirceWriter.scala b/logstage/logstage-rendering-circe/src/main/scala/izumi/logstage/api/rendering/json/LogstageCirceWriter.scala
new file mode 100644
index 000000000..47deb4ee4
--- /dev/null
+++ b/logstage/logstage-rendering-circe/src/main/scala/izumi/logstage/api/rendering/json/LogstageCirceWriter.scala
@@ -0,0 +1,117 @@
+package izumi.logstage.api.rendering.json
+
+import io.circe.Json
+import izumi.logstage.api.rendering.{LogstageCodec, LogstageWriter}
+import izumi.fundamentals.platform.language.Quirks._
+
+
+class LogstageCirceWriter extends LogstageWriter {
+  import LogstageCirceWriter._
+  private val stack = scala.collection.mutable.Stack[Token]()
+
+  def translate(): Json = {
+    val boundaries = scala.collection.mutable.Stack[Token]()
+
+    while (stack.nonEmpty) {
+      stack.pop() match {
+        case Token.Open(m) =>
+          val elements = scala.collection.mutable.ArrayBuffer[Json]()
+          while (boundaries.head != Token.Close) {
+            boundaries.pop() match {
+              case Token.Value(v) =>
+                elements += v
+              case t =>
+                throw new RuntimeException(s"Unexpected token: $t; stack=$stack, bstack=$boundaries")
+            }
+          }
+          boundaries.pop().discard()
+          if (m) {
+            val pairs = elements.sliding(2, 2).map {
+              e =>
+                (e.head.fold("null", _.toString, _.toString, identity, _.toString(), _.toString()), e.last)
+            }
+            boundaries.push(Token.Value(Json.fromFields(pairs.toIterable)))
+          } else {
+            boundaries.push(Token.Value(Json.fromValues(elements)))
+          }
+        case Token.Close =>
+          boundaries.push(Token.Close)
+        case v: Token.Value =>
+          boundaries.push(v)
+      }
+    }
+
+
+    boundaries.flatMap {
+      case _: Token.Struct =>
+        Seq.empty
+      case Token.Value(value) =>
+        Seq(value)
+    }.toList match {
+      case one :: Nil =>
+        one
+      case Nil =>
+        Json.Null
+      case shouldNotHappen =>
+        Json.fromValues(shouldNotHappen)
+    }
+  }
+
+  override def openList(): Unit = stack.push(Token.Open(false))
+
+  override def closeList(): Unit = stack.push(Token.Close)
+
+  override def openMap(): Unit = stack.push(Token.Open(true))
+
+  override def closeMap(): Unit = stack.push(Token.Close)
+
+  override def nextListElementClose(): Unit = {}
+
+  override def nextMapElementClose(): Unit = {}
+
+  override def mapElementSplitter(): Unit = {}
+
+  override def nextListElementOpen(): Unit = {}
+
+  override def nextMapElementOpen(): Unit = {}
+
+  override def write(a: Boolean): Unit = {}
+
+  override def writeNull(): Unit = stack.push(Token.Value(Json.Null))
+
+  override def write(a: Byte): Unit = stack.push(Token.Value(Json.fromInt(a.toInt)))
+
+  override def write(a: Short): Unit = stack.push(Token.Value(Json.fromInt(a.toInt)))
+
+  override def write(a: Char): Unit = stack.push(Token.Value(Json.fromString(a.toString)))
+
+  override def write(a: Int): Unit = stack.push(Token.Value(Json.fromInt(a)))
+
+  override def write(a: Long): Unit = stack.push(Token.Value(Json.fromLong(a)))
+
+  override def write(a: Float): Unit = stack.push(Token.Value(Json.fromFloatOrString(a)))
+
+  override def write(a: Double): Unit = stack.push(Token.Value(Json.fromDoubleOrString(a)))
+
+  override def write(a: String): Unit = stack.push(Token.Value(Json.fromString(a)))
+
+  override def write(a: BigDecimal): Unit = stack.push(Token.Value(Json.fromBigDecimal(a)))
+
+  override def write(a: BigInt): Unit = stack.push(Token.Value(Json.fromBigInt(a)))
+}
+
+object LogstageCirceWriter {
+  sealed trait Token
+  object Token {
+    sealed trait Struct extends Token
+    case class Value(value: Json) extends Token
+    case class Open(map: Boolean) extends Struct
+    case object Close extends Struct
+
+  }
+  def write(codec: LogstageCodec[Any], value: Any): Json = {
+    val writer = new LogstageCirceWriter()
+    codec.write(value, writer)
+    writer.translate()
+  }
+}
\ No newline at end of file
diff --git a/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/LogstageCirce.scala b/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/LogstageCirce.scala
index 37f62d387..66744b253 100644
--- a/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/LogstageCirce.scala
+++ b/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/LogstageCirce.scala
@@ -1,6 +1,7 @@
 package logstage.circe
 
-import izumi.logstage.api.rendering.json
+import io.circe.Encoder
+import izumi.logstage.api.rendering.{LogstageCodec, json}
 import izumi.logstage.sink.ConsoleSink
 
 import scala.language.implicitConversions
@@ -12,5 +13,12 @@ trait LogstageCirce {
 
   @inline implicit final def ToConsoleSinkJsonCtor(consoleSink: ConsoleSink.type): ConsoleSinkJsonCtor = new ConsoleSinkJsonCtor(consoleSink)
 
+
+  implicit val LogstageCirceJsonCodec: LogstageCirceCodec.LogstageCirceJsonCodec.type = LogstageCirceCodec.LogstageCirceJsonCodec
+  def fromCirce[T: Encoder]: LogstageCodec[T] = new LogstageCirceCodec[T]
 }
 
+
+
+
+
diff --git a/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/LogstageCirceCodec.scala b/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/LogstageCirceCodec.scala
new file mode 100644
index 000000000..acee32c77
--- /dev/null
+++ b/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/LogstageCirceCodec.scala
@@ -0,0 +1,63 @@
+package logstage.circe
+
+import io.circe.{Encoder, Json, JsonNumber, JsonObject}
+import izumi.logstage.api.rendering.{LogstageCodec, LogstageWriter}
+
+class LogstageCirceCodec[T : Encoder] extends LogstageCodec[T] {
+  override def write(value: T, writer: LogstageWriter): Unit = {
+    LogstageCirceCodec.LogstageCirceJsonCodec.write(implicitly[Encoder[T]].apply(value), writer)
+  }
+}
+
+object LogstageCirceCodec {
+  implicit object LogstageCirceJsonCodec extends LogstageCodec[Json] {
+
+    def folder(writer: LogstageWriter): Json.Folder[Unit] = new Json.Folder[Unit] {
+      override def onNull: Unit = writer.writeNull()
+
+      override def onBoolean(value: Boolean): Unit = writer.write(value)
+
+      override def onNumber(value: JsonNumber): Unit = {
+        value.toBigInt match {
+          case Some(value) =>
+            writer.write(value)
+          case None =>
+            value.toBigDecimal match {
+              case Some(value) =>
+                writer.write(value)
+              case None =>
+                writer.write(value.toDouble)
+            }
+        }
+      }
+
+      override def onString(value: String): Unit = writer.write(value)
+
+      override def onArray(value: Vector[Json]): Unit = {
+        writer.openList()
+        value.foreach {
+          v =>
+            writer.nextListElementOpen()
+            v.foldWith(this)
+            writer.nextListElementClose()
+        }
+        writer.closeList()
+      }
+
+      override def onObject(value: JsonObject): Unit = {
+        writer.openMap()
+        value.toIterable.foreach {
+          case (k, v) =>
+            writer.nextMapElementOpen()
+            writer.write(k)
+            writer.mapElementSplitter()
+            v.foldWith(this)
+            writer.nextMapElementClose()
+        }
+        writer.closeMap()
+      }
+    }
+
+    override def write(value: Json, writer: LogstageWriter): Unit = value.foldWith(folder(writer))
+  }
+}
\ No newline at end of file
diff --git a/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/package.scala b/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/package.scala
index 517ae07f3..8797c0791 100644
--- a/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/package.scala
+++ b/logstage/logstage-rendering-circe/src/main/scala/logstage/circe/package.scala
@@ -7,4 +7,5 @@ package object circe extends LogstageCirce {
   override type LogstageCirceRenderingPolicy = json.LogstageCirceRenderingPolicy
   override val LogstageCirceRenderingPolicy: json.LogstageCirceRenderingPolicy.type = json.LogstageCirceRenderingPolicy
 
+  override implicit val LogstageCirceJsonCodec: LogstageCirceCodec.LogstageCirceJsonCodec.type = LogstageCirceCodec.LogstageCirceJsonCodec
 }


Test patch:
diff --git a/logstage/logstage-core/src/test/scala/izumi/logstage/api/BasicLoggingTest.scala b/logstage/logstage-core/src/test/scala/izumi/logstage/api/BasicLoggingTest.scala
index 61a450c1f..e423e1da6 100644
--- a/logstage/logstage-core/src/test/scala/izumi/logstage/api/BasicLoggingTest.scala
+++ b/logstage/logstage-core/src/test/scala/izumi/logstage/api/BasicLoggingTest.scala
@@ -2,7 +2,7 @@ package izumi.logstage.api
 
 import izumi.fundamentals.platform.language.SourceFilePosition
 import izumi.logstage.api.Log._
-import izumi.logstage.api.rendering.{RenderingOptions, StringRenderingPolicy}
+import izumi.logstage.api.rendering.{LogstageCodec, RenderingOptions, StringRenderingPolicy}
 import org.scalatest.wordspec.AnyWordSpec
 
 import scala.util.Random
@@ -16,11 +16,11 @@ class BasicLoggingTest extends AnyWordSpec {
 
       val message = Message(s"argument1: $arg1, argument2: $arg2, argument2 again: $arg2, expression ${2 + 2}, ${2 + 2}")
       val expectation = List(
-        LogArg(Seq("arg1"), 1, hiddenName = false, None),
-        LogArg(Seq("arg2"), "argument 2", hiddenName = false, None),
-        LogArg(Seq("arg2"), "argument 2", hiddenName = false, None),
-        LogArg(Seq("UNNAMED:4"), 4, hiddenName = false, None),
-        LogArg(Seq("UNNAMED:4"), 4, hiddenName = false, None),
+        LogArg(Seq("arg1"), 1, hiddenName = false, Some(LogstageCodec.LogstageCodecInt)),
+        LogArg(Seq("arg2"), "argument 2", hiddenName = false, Some(LogstageCodec.LogstageCodecString)),
+        LogArg(Seq("arg2"), "argument 2", hiddenName = false, Some(LogstageCodec.LogstageCodecString)),
+        LogArg(Seq("UNNAMED:4"), 4, hiddenName = false, Some(LogstageCodec.LogstageCodecInt)),
+        LogArg(Seq("UNNAMED:4"), 4, hiddenName = false, Some(LogstageCodec.LogstageCodecInt)),
       )
 
       val expectedParts = List("argument1: ", ", argument2: ", ", argument2 again: ", ", expression ", ", ", "")
@@ -41,7 +41,7 @@ class BasicLoggingTest extends AnyWordSpec {
            |multiline ${m -> "message"}""".stripMargin
       }
       assert(message1.template.parts.toList == List("This\nis a\nmultiline ", ""))
-      assert(message1.args == List(LogArg(Seq("message"), m, hiddenName = false, None)))
+      assert(message1.args == List(LogArg(Seq("message"), m, hiddenName = false, Some(LogstageCodec.LogstageCodecString))))
 
       val message2 = Message("single line with stripMargin".stripMargin)
       assert(message2.template.parts.toList == List("single line with stripMargin"))
@@ -71,7 +71,7 @@ class BasicLoggingTest extends AnyWordSpec {
       val s = "hi"
       val msg = Message(s"begin $i $s end")
 
-      assert(msg == Message(StringContext("begin ", " ", " end"), Seq(LogArg(Seq("i"), 5, hiddenName = false, None), LogArg(Seq("s"), "hi", hiddenName = false, None))))
+      assert(msg == Message(StringContext("begin ", " ", " end"), Seq(LogArg(Seq("i"), 5, hiddenName = false,  Some(LogstageCodec.LogstageCodecInt)), LogArg(Seq("s"), "hi", hiddenName = false,  Some(LogstageCodec.LogstageCodecString)))))
     }
   }
 
diff --git a/logstage/logstage-rendering-circe/src/test/scala/izumi/logstage/api/rendering/json/LogstageCirceRenderingTest.scala b/logstage/logstage-rendering-circe/src/test/scala/izumi/logstage/api/rendering/json/LogstageCirceRenderingTest.scala
index b055e7b04..f764f09b2 100644
--- a/logstage/logstage-rendering-circe/src/test/scala/izumi/logstage/api/rendering/json/LogstageCirceRenderingTest.scala
+++ b/logstage/logstage-rendering-circe/src/test/scala/izumi/logstage/api/rendering/json/LogstageCirceRenderingTest.scala
@@ -1,12 +1,17 @@
 package izumi.logstage.api.rendering.json
 
-import izumi.logstage.api.{IzLogger, TestSink}
-import izumi.logstage.sink.{ConsoleSink, ExampleService}
+import io.circe.{Codec, Json}
 import io.circe.literal._
 import io.circe.parser._
+import izumi.logstage.api.rendering.{LogstageCodec, LogstageWriter}
+import izumi.logstage.api.strict.IzStrictLogger
+import izumi.logstage.api.{IzLogger, TestSink}
+import izumi.logstage.sink.{ConsoleSink, ExampleService}
 import org.scalatest.wordspec.AnyWordSpec
 
+
 class LogstageCirceRenderingTest extends AnyWordSpec {
+  import LogstageCirceRenderingTest._
 
   val debug = false
 
@@ -22,7 +27,7 @@ class LogstageCirceRenderingTest extends AnyWordSpec {
       val renderedMessages = sink.fetchRendered()
       assert(renderedMessages.nonEmpty)
 
-      renderedMessages.foreach{
+      renderedMessages.foreach {
         s =>
           parse(s) match {
             case Left(value) =>
@@ -41,6 +46,32 @@ class LogstageCirceRenderingTest extends AnyWordSpec {
 
     }
 
+    "support strict logging" in {
+      val (logger, sink) = setupJsonStrictLogger(debug)
+
+      val list = List("a", "b", "c")
+      val withCustomCodec = WithCustomCodec()
+      val withCustomDerivedCodec = WithCustomDerivedCodec(42, "hatersgonnahate", List(Map("kudah" -> "kukarek")))
+
+      val subLogger = logger("list" -> list, "custom" -> withCustomCodec, "derived" -> withCustomDerivedCodec)
+
+      subLogger.info(s"${list -> "list"}, ${withCustomCodec -> "custom"}, ${withCustomDerivedCodec -> "derived"}")
+
+      val renderedMessages = sink.fetchRendered()
+      assert(renderedMessages.nonEmpty)
+      val eventJson = parse(renderedMessages.head).right.get.hcursor
+      val data = eventJson.downField("event").focus.get.asObject.map(_.toMap).get
+      val context = eventJson.downField("context").focus.get.asObject.map(_.toMap).get
+
+      def verifyBlock(d: Map[String, Json]) = {
+        assert(d("list") == json"""["a","b","c"]""")
+        assert(d("custom") == json"""{"a":[1,"b"]}""")
+        assert(d("derived") == json"""{"a":42,"b":"hatersgonnahate","c":[{"kudah":"kukarek"}]}""")
+      }
+
+      verifyBlock(data)
+      verifyBlock(context)
+    }
   }
 
 
@@ -57,6 +88,41 @@ class LogstageCirceRenderingTest extends AnyWordSpec {
     (IzLogger(IzLogger.Level.Trace, sinks), testSink)
   }
 
+  def setupJsonStrictLogger(debug: Boolean): (IzStrictLogger, TestSink) = {
+    val jsonPolicy = new LogstageCirceRenderingPolicy(prettyPrint = true)
+    val testSink = new TestSink(Some(jsonPolicy))
+
+    val sinks = if (debug) {
+      Seq(testSink, new ConsoleSink(jsonPolicy))
+    } else {
+      Seq(testSink)
+    }
+
+    (IzStrictLogger(IzLogger.Level.Trace, sinks), testSink)
+  }
 }
 
 
+object LogstageCirceRenderingTest {
+  case class WithCustomCodec()
+
+  object WithCustomCodec {
+    implicit val Codec: LogstageCodec[WithCustomCodec] = (_: WithCustomCodec, writer: LogstageWriter) => {
+      writer.openMap()
+      writer.write("a")
+      writer.openList()
+      writer.write(1)
+      writer.write("b")
+      writer.closeList()
+      writer.closeMap()
+    }
+  }
+
+  case class WithCustomDerivedCodec(a: Int, b: String, c: List[Map[String, String]])
+
+  object WithCustomDerivedCodec {
+    implicit val JsonCodec: Codec.AsObject[WithCustomDerivedCodec] = _root_.io.circe.derivation.deriveCodec[WithCustomDerivedCodec]
+    implicit val LsCodec: LogstageCodec[WithCustomDerivedCodec] = _root_.logstage.circe.fromCirce[WithCustomDerivedCodec]
+  }
+
+}
\ No newline at end of file