diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..74682c6 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{kt,kts}] +indent_size = 4 +ktlint_code_style = intellij_idea +ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = unset +ktlint_ignore_back_ticked_identifier = true +ktlint_function_signature_body_expression_wrapping = default \ No newline at end of file diff --git a/klog/src/main/kotlin/com/coditory/klog/KlogContext.kt b/klog/src/main/kotlin/com/coditory/klog/KlogContext.kt index bebcdf9..3febdee 100644 --- a/klog/src/main/kotlin/com/coditory/klog/KlogContext.kt +++ b/klog/src/main/kotlin/com/coditory/klog/KlogContext.kt @@ -24,10 +24,9 @@ internal data class KlogContext( companion object { fun build(config: KlogConfig): KlogContext { return KlogContext( - streams = - config.streams.mapIndexed { idx, stream -> - buildStream(idx, stream, config) - }, + streams = config.streams.mapIndexed { idx, stream -> + buildStream(idx, stream, config) + }, clock = config.clock, listener = config.listener, klogErrLogger = config.klogErrLogger, @@ -43,12 +42,11 @@ internal data class KlogContext( val streamDescriptor = LogStreamDescriptor(streamIdx, config.filter) val publishers = config.publishers.mapIndexed { idx, publisher -> - val descriptor = - LogPublisherDescriptor( - stream = streamDescriptor, - publisherIdx = idx, - publisherType = publisher.javaClass, - ) + val descriptor = LogPublisherDescriptor( + stream = streamDescriptor, + publisherIdx = idx, + publisherType = publisher.javaClass, + ) buildPublisher(descriptor, publisher, klogConfig) } return LogStream( diff --git a/klog/src/main/kotlin/com/coditory/klog/format/DurationFormat.kt b/klog/src/main/kotlin/com/coditory/klog/format/DurationFormat.kt new file mode 100644 index 0000000..3ded803 --- /dev/null +++ b/klog/src/main/kotlin/com/coditory/klog/format/DurationFormat.kt @@ -0,0 +1,71 @@ +package com.coditory.klog.format + +import java.time.Duration +import kotlin.math.abs +import kotlin.math.round + +object DurationFormat { + fun format(duration: Duration): String { + val ms = duration.toMillis() + return if (duration.isZero || ms != 0L) { + formatMillis(ms) + } else { + "${duration.toNanos()}ns" + } + } + + fun formatMillis(ms: Long): String { + if (ms == 0L) { + return "0.00s" + } + val seconds = ms / 1000 + val minutes = seconds / 60 + val hours = minutes / 60 + val days = hours / 24 + return if (abs(days) > 0) { + val v = round(hours * 100 / 24.0) / 100.0 + "%.2fd".format(v) + } else if (abs(hours) > 0) { + val v = round(minutes * 100 / 60.0) / 100.0 + "%.2fh".format(v) + } else if (abs(minutes) > 0) { + val v = round(seconds * 100 / 60.0) / 100.0 + "%.2fm".format(v) + } else if (abs(seconds) > 0) { + val v = round(ms * 100 / 1000.0) / 100.0 + "%.2fs".format(v) + } else { + "${ms}ms" + } + } + + fun formatSignificant(duration: Duration): String { + val ms = duration.toMillis() + return if (duration.isZero || ms != 0L) { + formatMillisSignificant(ms) + } else { + "${duration.toNanos()}ns" + } + } + + fun formatMillisSignificant(ms: Long): String { + if (ms == 0L) { + return "0s" + } + val seconds = ms / 1000 + val minutes = seconds / 60 + val hours = minutes / 60 + val days = hours / 24 + return if (abs(days) > 0) { + "${days}d" + } else if (abs(hours) > 0) { + "${hours}h" + } else if (abs(minutes) > 0) { + "${minutes}m" + } else if (abs(seconds) > 0) { + "${seconds}s" + } else { + "${ms}ms" + } + } +} diff --git a/klog/src/main/kotlin/com/coditory/klog/publish/InMemoryTextPublisher.kt b/klog/src/main/kotlin/com/coditory/klog/publish/InMemoryTextPublisher.kt index c2363b3..57da85b 100644 --- a/klog/src/main/kotlin/com/coditory/klog/publish/InMemoryTextPublisher.kt +++ b/klog/src/main/kotlin/com/coditory/klog/publish/InMemoryTextPublisher.kt @@ -3,6 +3,9 @@ package com.coditory.klog.publish import com.coditory.klog.LogEvent import com.coditory.klog.text.TextLogEventSerializer import com.coditory.klog.text.plain.PlainTextLogEventSerializer +import com.coditory.klog.text.plain.PlainTextTimestampFormatter +import java.time.ZoneId +import java.time.format.DateTimeFormatter class InMemoryTextPublisher( private val serializer: TextLogEventSerializer = PlainTextLogEventSerializer(), @@ -55,6 +58,9 @@ class InMemoryTextPublisher( fun testPublisher(): InMemoryTextPublisher { return InMemoryTextPublisher( PlainTextLogEventSerializer( + timestampFormatter = PlainTextTimestampFormatter.from( + DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("UTC")), + ), threadFormatter = { _, appendable -> appendable.append(TEST_THREAD_NAME) }, ), ) diff --git a/klog/src/main/kotlin/com/coditory/klog/text/plain/PlainTextLogEventSerializer.kt b/klog/src/main/kotlin/com/coditory/klog/text/plain/PlainTextLogEventSerializer.kt index 7051fa3..794ab39 100644 --- a/klog/src/main/kotlin/com/coditory/klog/text/plain/PlainTextLogEventSerializer.kt +++ b/klog/src/main/kotlin/com/coditory/klog/text/plain/PlainTextLogEventSerializer.kt @@ -35,6 +35,7 @@ class PlainTextLogEventSerializer( if (field.skip(event, mergeContextToItems)) continue if (length < sized.length()) { if (field == LogEventField.MESSAGE) { + sized.appendLazy(null) sized.append(messageSeparator) } else { sized.appendLazy(' ') @@ -124,10 +125,16 @@ class PlainTextLogEventSerializer( fields = LogEventField.all(), timestampFormatter = PlainTextTimestampFormatter.fromLocalTime(timestampStyle), levelFormatter = levelFormatter, - loggerNameFormatter = PlainTextStringFormatter.builder().ansi(ansiResolved).compactSections().build(), - threadFormatter = - PlainTextStringFormatter.builder().ansi(ansiResolved).style(threadStyle).prefix("[") - .postfix("]").build(), + loggerNameFormatter = PlainTextStringFormatter.builder() + .ansi(ansiResolved) + .compactSections() + .build(), + threadFormatter = PlainTextStringFormatter.builder() + .ansi(ansiResolved) + .style(threadStyle) + .prefix("[") + .postfix("]") + .build(), ) } diff --git a/klog/src/main/kotlin/com/coditory/klog/text/shared/SizedAppendable.kt b/klog/src/main/kotlin/com/coditory/klog/text/shared/SizedAppendable.kt index d604cb5..442e280 100644 --- a/klog/src/main/kotlin/com/coditory/klog/text/shared/SizedAppendable.kt +++ b/klog/src/main/kotlin/com/coditory/klog/text/shared/SizedAppendable.kt @@ -15,7 +15,7 @@ internal class SizedAppendable( return length } - fun appendLazy(csq: CharSequence): Appendable { + fun appendLazy(csq: CharSequence?): Appendable { lazyAppend = csq lazyAppendChar = null return this diff --git a/klog/src/test/kotlin/com/coditory/klog/KlogComplexPlainTextSpec.kt b/klog/src/test/kotlin/com/coditory/klog/KlogComplexPlainTextSpec.kt index 57b45d7..c9c3033 100644 --- a/klog/src/test/kotlin/com/coditory/klog/KlogComplexPlainTextSpec.kt +++ b/klog/src/test/kotlin/com/coditory/klog/KlogComplexPlainTextSpec.kt @@ -3,7 +3,7 @@ package com.coditory.klog import com.coditory.klog.publish.InMemoryTextPublisher import com.coditory.klog.publish.InMemoryTextPublisher.Companion.TEST_THREAD_NAME import com.coditory.klog.shared.UpdatableFixedClock -import com.coditory.klog.shared.UpdatableFixedClock.Companion.DEFAULT_FIXED_TIME_LOG_STR +import com.coditory.klog.shared.UpdatableFixedClock.Companion.DEFAULT_FIXED_TIME_STR import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe @@ -27,6 +27,6 @@ class KlogComplexPlainTextSpec : FunSpec({ test("should emit a basic info log event") { logger.info { "Hello" } publisher.getLogs().size shouldBe 1 - publisher.getLastLog() shouldBe "$DEFAULT_FIXED_TIME_LOG_STR INFO $TEST_THREAD_NAME com.coditory.Logger: Hello" + publisher.getLastLog() shouldBe "$DEFAULT_FIXED_TIME_STR INFO $TEST_THREAD_NAME com.coditory.Logger: Hello" } }) diff --git a/klog/src/test/kotlin/com/coditory/klog/format/DurationFormatSpec.kt b/klog/src/test/kotlin/com/coditory/klog/format/DurationFormatSpec.kt new file mode 100644 index 0000000..b939604 --- /dev/null +++ b/klog/src/test/kotlin/com/coditory/klog/format/DurationFormatSpec.kt @@ -0,0 +1,62 @@ +package com.coditory.klog.format + +import io.kotest.core.spec.style.FunSpec +import io.kotest.core.tuple +import io.kotest.matchers.shouldBe +import java.time.Duration + +class DurationFormatSpec : FunSpec({ + listOf( + tuple(Duration.ofDays(0), "0.00s"), + tuple(Duration.ofDays(-3), "-3.00d"), + tuple(Duration.ofDays(3), "3.00d"), + tuple(Duration.ofHours(36), "1.50d"), + tuple(Duration.ofHours(24), "1.00d"), + tuple(Duration.ofHours(16), "16.00h"), + tuple(Duration.ofMinutes(110), "1.83h"), + tuple(Duration.ofMinutes(60), "1.00h"), + tuple(Duration.ofMinutes(35), "35.00m"), + tuple(Duration.ofSeconds(110), "1.83m"), + tuple(Duration.ofSeconds(60), "1.00m"), + tuple(Duration.ofSeconds(35), "35.00s"), + tuple(Duration.ofMillis(1100), "1.10s"), + tuple(Duration.ofMillis(1000), "1.00s"), + tuple(Duration.ofMillis(200), "200ms"), + tuple(Duration.ofNanos(1100000), "1ms"), + tuple(Duration.ofNanos(1000000), "1ms"), + tuple(Duration.ofNanos(1000), "1000ns"), + tuple(Duration.ofNanos(200), "200ns"), + tuple(Duration.ofNanos(-200), "-200ns"), + ).forEach { + test("format: ${it.a} -> \"${it.b}\"") { + DurationFormat.format(it.a) shouldBe it.b + } + } + + listOf( + tuple(Duration.ofDays(0), "0s"), + tuple(Duration.ofDays(-3), "-3d"), + tuple(Duration.ofDays(3), "3d"), + tuple(Duration.ofHours(36), "1d"), + tuple(Duration.ofHours(24), "1d"), + tuple(Duration.ofHours(16), "16h"), + tuple(Duration.ofMinutes(110), "1h"), + tuple(Duration.ofMinutes(60), "1h"), + tuple(Duration.ofMinutes(35), "35m"), + tuple(Duration.ofSeconds(110), "1m"), + tuple(Duration.ofSeconds(60), "1m"), + tuple(Duration.ofSeconds(35), "35s"), + tuple(Duration.ofMillis(1100), "1s"), + tuple(Duration.ofMillis(1000), "1s"), + tuple(Duration.ofMillis(200), "200ms"), + tuple(Duration.ofNanos(1100000), "1ms"), + tuple(Duration.ofNanos(1000000), "1ms"), + tuple(Duration.ofNanos(1000), "1000ns"), + tuple(Duration.ofNanos(200), "200ns"), + tuple(Duration.ofNanos(-200), "-200ns"), + ).forEach { + test("format significant: ${it.a} -> \"${it.b}\"") { + DurationFormat.formatSignificant(it.a) shouldBe it.b + } + } +}) diff --git a/klog/src/test/kotlin/com/coditory/klog/shared/UpdatableFixedClock.kt b/klog/src/test/kotlin/com/coditory/klog/shared/UpdatableFixedClock.kt index eedb701..de8f37d 100644 --- a/klog/src/test/kotlin/com/coditory/klog/shared/UpdatableFixedClock.kt +++ b/klog/src/test/kotlin/com/coditory/klog/shared/UpdatableFixedClock.kt @@ -32,8 +32,8 @@ class UpdatableFixedClock( companion object { // Always use instant with nanos for testing. Some databases (like mongo) trim nanos - you should test for that! - val DEFAULT_FIXED_TIME_LOG_STR = "2015-12-03T11:15:30.123456+01:00" - val DEFAULT_FIXED_TIME = Instant.parse("2015-12-03T10:15:30.123456Z") + val DEFAULT_FIXED_TIME_STR = "2015-12-03T10:15:30.123456Z" + val DEFAULT_FIXED_TIME = Instant.parse(DEFAULT_FIXED_TIME_STR) val DEFAULT_ZONE_ID = ZoneId.of("Europe/Warsaw") } }