diff options
author | Linnea Gräf <nea@nea.moe> | 2025-01-08 19:25:29 +0100 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2025-01-08 19:25:29 +0100 |
commit | d1e16a47819509ed645bb93e1a173e0a97025cef (patch) | |
tree | efbe886d9ac1ab4ea01788cb4842812fd0af9079 /mod/src/main/kotlin/moe/nea/ledger/utils | |
parent | f694daf322bbb4ff530a9332547c5c8337c3e0c0 (diff) | |
download | LocalTransactionLedger-d1e16a47819509ed645bb93e1a173e0a97025cef.tar.gz LocalTransactionLedger-d1e16a47819509ed645bb93e1a173e0a97025cef.tar.bz2 LocalTransactionLedger-d1e16a47819509ed645bb93e1a173e0a97025cef.zip |
build: Move mod to subproject
Diffstat (limited to 'mod/src/main/kotlin/moe/nea/ledger/utils')
20 files changed, 637 insertions, 0 deletions
diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/BorderedTextTracker.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/BorderedTextTracker.kt new file mode 100644 index 0000000..9e621e8 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/BorderedTextTracker.kt @@ -0,0 +1,41 @@ +package moe.nea.ledger.utils + +import moe.nea.ledger.events.ChatReceived +import moe.nea.ledger.utils.di.Inject +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent + +abstract class BorderedTextTracker { + + val genericBorderExit = "▬{10,}".toPattern() + + @Inject + lateinit var errorUtil: ErrorUtil + var stack: MutableList<ChatReceived>? = null + + + @SubscribeEvent + fun receiveText(event: ChatReceived) { + if (stack != null && shouldExit(event)) { + exit() + return + } + if (shouldEnter(event)) { + if (stack != null) { + errorUtil.reportAdHoc("Double entered a bordered message") + exit() + } + stack = mutableListOf() + } + stack?.add(event) + } + + private fun exit() { + onBorderedTextFinished(stack!!) + stack = null + } + + abstract fun shouldEnter(event: ChatReceived): Boolean + abstract fun shouldExit(event: ChatReceived): Boolean + abstract fun onBorderedTextFinished(enclosed: List<ChatReceived>) + +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt new file mode 100644 index 0000000..e0c83f9 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt @@ -0,0 +1,52 @@ +package moe.nea.ledger.utils + +import moe.nea.ledger.utils.di.Inject +import moe.nea.ledger.utils.telemetry.ContextValue +import moe.nea.ledger.utils.telemetry.EventRecorder +import moe.nea.ledger.utils.telemetry.Span +import java.util.concurrent.CompletableFuture + +class ErrorUtil { + + @Inject + lateinit var reporter: EventRecorder + + fun reportAdHoc(message: String) { + report(Exception(message), message) + + } + + fun report(exception: Throwable, message: String?) { + Span.current().recordException(reporter, exception, message) + } + + fun <T> Result<T>.getOrReport(): T? { + val exc = exceptionOrNull() + if (exc != null) { + report(exc, null) + } + return getOrNull() + } + + fun <T : CompletableFuture<*>> listenToFuture(t: T): T { + t.handle { ignored, exception -> + if (exception != null) + report(exception, "Uncaught exception in completable future") + } + return t + } + + inline fun <T> catch( + vararg pairs: Pair<String, ContextValue>, + crossinline function: () -> T + ): T? { + return Span.current().enterWith(*pairs) { + try { + return@enterWith function() + } catch (ex: Exception) { + report(ex, null) + return@enterWith null + } + } + } +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt new file mode 100644 index 0000000..d3c1f6e --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt @@ -0,0 +1,10 @@ +package moe.nea.ledger.utils + +import com.google.gson.reflect.TypeToken + +object GsonUtil { + inline fun <reified T> typeToken(): TypeToken<T> { + return object : TypeToken<T>() {} + } + +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/MinecraftExecutor.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/MinecraftExecutor.kt new file mode 100644 index 0000000..affd86c --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/MinecraftExecutor.kt @@ -0,0 +1,10 @@ +package moe.nea.ledger.utils + +import net.minecraft.client.Minecraft +import java.util.concurrent.Executor + +class MinecraftExecutor : Executor { + override fun execute(command: Runnable) { + Minecraft.getMinecraft().addScheduledTask(command) + } +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/NoSideEffects.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/NoSideEffects.kt new file mode 100644 index 0000000..9b0e7a3 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/NoSideEffects.kt @@ -0,0 +1,4 @@ +package moe.nea.ledger.utils + +@Retention(AnnotationRetention.BINARY) +annotation class NoSideEffects diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt new file mode 100644 index 0000000..ddf2fcc --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt @@ -0,0 +1,40 @@ +package moe.nea.ledger.utils.network + +import com.google.gson.JsonElement +import java.net.URL + +data class Request( + val url: URL, + val method: Method, + val body: String?, + val headers: Map<String, String>, +) { + enum class Method { + GET, POST + } + + enum class MediaType(val text: String) { + JSON("application/json"), + TEXT("text/plain"), + HTML("text/html"), + ANY("*/*"), + } + + fun withHeaders(map: Map<String, String>): Request { + // TODO: enforce caselessness? + return this.copy(headers = headers + map) + } + + fun post() = copy(method = Method.POST) + fun get() = copy(method = Method.GET) + + fun json(element: JsonElement) = copy( + headers = headers + mapOf("content-type" to "application/json"), + body = element.toString()) + + fun accept(request: MediaType) = withHeaders(mapOf("accept" to request.text)) + + fun acceptJson() = accept(MediaType.JSON) + + fun execute(requestUtil: RequestUtil) = requestUtil.executeRequest(this) +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt new file mode 100644 index 0000000..a49c65a --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt @@ -0,0 +1,63 @@ +package moe.nea.ledger.utils.network + +import moe.nea.ledger.utils.ErrorUtil +import moe.nea.ledger.utils.di.Inject +import java.net.URL +import java.net.URLConnection +import java.security.KeyStore +import java.util.zip.GZIPInputStream +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory + +class RequestUtil @Inject constructor(val errorUtil: ErrorUtil) { + + private fun createSSLContext(): SSLContext? = errorUtil.catch { + val keyStorePath = RequestUtil::class.java.getResourceAsStream("/ledgerkeystore.jks") + ?: error("Could not locate keystore") + val keyStore = KeyStore.getInstance("JKS") + keyStore.load(keyStorePath, "neuneu".toCharArray()) + val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + kmf.init(keyStore, null) + tmf.init(keyStore) + val ctx = SSLContext.getInstance("TLS") + ctx.init(kmf.keyManagers, tmf.trustManagers, null) + return@catch ctx + } + + val sslContext = createSSLContext() + + fun enhanceConnection(connection: URLConnection) { + if (connection is HttpsURLConnection && sslContext != null) { + connection.sslSocketFactory = sslContext.socketFactory + } + } + + fun createRequest(url: String) = createRequest(URL(url)) + fun createRequest(url: URL) = Request(url, Request.Method.GET, null, mapOf()) + + fun executeRequest(request: Request): Response { + val connection = request.url.openConnection() + enhanceConnection(connection) + connection.setRequestProperty("accept-encoding", "gzip") + request.headers.forEach { (k, v) -> + connection.setRequestProperty(k, v) + } + if (request.body != null) { + connection.getOutputStream().write(request.body.encodeToByteArray()) + connection.getOutputStream().close() + } + var stream = connection.getInputStream() + if (connection.contentEncoding == "gzip") { + stream = GZIPInputStream(stream) + } + val text = stream.bufferedReader().readText() + stream.close() + // Do NOT call connection.disconnect() to allow for connection reuse + return Response(request, text, connection.headerFields) + } + + +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt new file mode 100644 index 0000000..daae7f7 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt @@ -0,0 +1,19 @@ +package moe.nea.ledger.utils.network + +import com.google.gson.reflect.TypeToken +import moe.nea.ledger.Ledger + +data class Response( + val source: Request, + // TODO: allow other body processors, to avoid loading everything as strings + val response: String, + val headers: Map<String, List<String>>, +) { + fun <T : Any> json(typ: TypeToken<T>): T { + return Ledger.gson.fromJson(response, typ.type) + } + + fun <T : Any> json(clazz: Class<T>): T { + return Ledger.gson.fromJson(response, clazz) + } +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/BooleanContext.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/BooleanContext.kt new file mode 100644 index 0000000..5f4ccdf --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/BooleanContext.kt @@ -0,0 +1,10 @@ +package moe.nea.ledger.utils.telemetry + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive + +class BooleanContext(val boolean: Boolean) : ContextValue { + override fun serialize(): JsonElement { + return JsonPrimitive(boolean) + } +} diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/CommonKeys.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/CommonKeys.kt new file mode 100644 index 0000000..004ae9c --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/CommonKeys.kt @@ -0,0 +1,9 @@ +package moe.nea.ledger.utils.telemetry + +object CommonKeys { + val EVENT_MESSAGE = "event_message" + val EXCEPTION = "event_exception" + val COMMIT_VERSION = "version_commit" + val VERSION = "version" + val PHASE = "phase" // TODO: add a sort of "manual" stacktrace with designated function phases +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Context.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Context.kt new file mode 100644 index 0000000..3c30a52 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Context.kt @@ -0,0 +1,57 @@ +package moe.nea.ledger.utils.telemetry + +import com.google.gson.JsonObject + +class Context(val data: MutableMap<String, ContextValue> = mutableMapOf()) : ContextValue.Collatable<Context> { + + inline fun <reified T : ContextValue> getT(key: String): T? { + return get(key) as? T + } + + fun get(key: String): ContextValue? { + return data[key] + } + + fun add(key: String, value: ContextValue) { + data[key] = value + } + + @Suppress("NOTHING_TO_INLINE") + private inline fun <T : ContextValue.Collatable<T>> cope( + left: ContextValue.Collatable<T>, + right: ContextValue + ): ContextValue { + return try { + left.combineWith(right as T) + } catch (ex: Exception) { + // TODO: cope with this better + right + } + } + + override fun combineWith(overrides: Context): Context { + val copy = data.toMutableMap() + for ((key, overrideValue) in overrides.data) { + copy.merge(key, overrideValue) { old, new -> + if (old is ContextValue.Collatable<*>) { + cope(old, new) + } else { + new + } + } + } + return Context(copy) + } + + override fun actualize(): Context { + return this + } + + override fun serialize(): JsonObject { + val obj = JsonObject() + data.forEach { (k, v) -> + obj.add(k, v.serialize()) + } + return obj + } +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/ContextValue.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/ContextValue.kt new file mode 100644 index 0000000..b5891fc --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/ContextValue.kt @@ -0,0 +1,70 @@ +package moe.nea.ledger.utils.telemetry + +import com.google.gson.JsonElement +import com.google.gson.JsonObject + +interface ContextValue { + companion object { + fun <T : Collatable<T>> lazyCollatable(value: () -> Collatable<T>): Collatable<T> { + return LazyCollatable(value) + } + + fun lazy(value: () -> ContextValue): ContextValue { + return object : ContextValue { + val value by kotlin.lazy(value) + override fun serialize(): JsonElement { + return this.value.serialize() + } + } + } + + fun bool(boolean: Boolean): ContextValue { + return BooleanContext(boolean) + } + + fun string(message: String): ContextValue { + return StringContext(message) + } + + fun jsonObject(vararg pairs: Pair<String, JsonElement>): ContextValue { + val obj = JsonObject() + for ((l, r) in pairs) { + obj.add(l, r) + } + return JsonElementContext(obj) + } + + fun compound(vararg pairs: Pair<String, String>): ContextValue { + val obj = JsonObject() + for ((l, r) in pairs) { + obj.addProperty(l, r) + } + // TODO: should this be its own class? + return JsonElementContext(obj) + } + } + + // TODO: allow other serialization formats + fun serialize(): JsonElement + interface Collatable<T : Collatable<T>> : ContextValue { + fun combineWith(overrides: T): T + fun actualize(): T + } + + private class LazyCollatable<T : Collatable<T>>( + provider: () -> Collatable<T>, + ) : Collatable<T> { + val value by kotlin.lazy(provider) + override fun actualize(): T { + return value.actualize() + } + + override fun combineWith(overrides: T): T { + return value.combineWith(overrides) + } + + override fun serialize(): JsonElement { + return value.serialize() + } + } +} diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/EventRecorder.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/EventRecorder.kt new file mode 100644 index 0000000..28b1ab5 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/EventRecorder.kt @@ -0,0 +1,9 @@ +package moe.nea.ledger.utils.telemetry + +interface EventRecorder { + companion object { + var instance: EventRecorder? = null + } + + fun record(event: RecordedEvent) +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt new file mode 100644 index 0000000..96b70ec --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt @@ -0,0 +1,39 @@ +package moe.nea.ledger.utils.telemetry + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject + +class ExceptionContextValue(val exception: Throwable) : ContextValue { + val stackTrace by lazy { + exception.stackTraceToString() + } + + override fun serialize(): JsonElement { + val jsonObject = JsonObject() + jsonObject.addProperty("exception_stackTrace", stackTrace) + jsonObject.add("exception_structure", walkExceptions(exception, 6)) + return jsonObject + } + + private fun walkExceptions(exception: Throwable, searchDepth: Int): JsonElement { + val obj = JsonObject() + obj.addProperty("class", exception.javaClass.name) + obj.addProperty("message", exception.message) + // TODO: allow exceptions to implement an "extra info" interface + if (searchDepth > 0) { + val cause = exception.cause + if (cause != null && cause !== exception) { + obj.add("cause", walkExceptions(cause, searchDepth - 1)) + } + val suppressions = JsonArray() + for (suppressedException in exception.suppressedExceptions) { + suppressions.add(walkExceptions(suppressedException, searchDepth - 1)) + } + if (suppressions.size() > 0) { + obj.add("suppressions", suppressions) + } + } + return obj + } +} diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/JsonElementContext.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/JsonElementContext.kt new file mode 100644 index 0000000..1601f56 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/JsonElementContext.kt @@ -0,0 +1,9 @@ +package moe.nea.ledger.utils.telemetry + +import com.google.gson.JsonElement + +class JsonElementContext(val element: JsonElement) : ContextValue { + override fun serialize(): JsonElement { + return element + } +} diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/LoggingEventRecorder.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/LoggingEventRecorder.kt new file mode 100644 index 0000000..82a76ed --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/LoggingEventRecorder.kt @@ -0,0 +1,25 @@ +package moe.nea.ledger.utils.telemetry + +import com.google.gson.GsonBuilder +import org.apache.logging.log4j.Logger + +class LoggingEventRecorder( + val logger: Logger, + val logJson: Boolean +) : EventRecorder { + companion object { + private val gson = GsonBuilder().setPrettyPrinting().create() + } + + override fun record(event: RecordedEvent) { + val exc = event.context.getT<ExceptionContextValue>(CommonKeys.EXCEPTION) + var message = "Event Recorded: " + event.context.getT<StringContext>(CommonKeys.EVENT_MESSAGE)?.message + if (logJson) { + message += "\n" + gson.toJson(event.context.serialize()) + } + if (exc != null) + logger.error(message, exc.exception) + else + logger.warn(message) + } +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/RecordedEvent.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/RecordedEvent.kt new file mode 100644 index 0000000..346417d --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/RecordedEvent.kt @@ -0,0 +1,5 @@ +package moe.nea.ledger.utils.telemetry + +class RecordedEvent(val context: Context) { + +} diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Severity.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Severity.kt new file mode 100644 index 0000000..e9a3b79 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Severity.kt @@ -0,0 +1,8 @@ +package moe.nea.ledger.utils.telemetry + +enum class Severity { + INFO, + WARN, + ERROR, + CRITICAL, +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Span.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Span.kt new file mode 100644 index 0000000..0d680a9 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/Span.kt @@ -0,0 +1,146 @@ +package moe.nea.ledger.utils.telemetry + +class Span(val parent: Span?) : AutoCloseable { + companion object { + private val _current = object : InheritableThreadLocal<Span>() { + override fun initialValue(): Span { + return Span(null) + } + + override fun childValue(parentValue: Span?): Span { + return parentValue?.forkNewRoot() ?: initialValue() + } + } + + fun current(): Span { + return _current.get() + } + } + + private val data = Context() + + // TODO : replace string key with a SpanKey<T> class + fun add(key: String, value: ContextValue) { + data.add(key, value) + } + + /** + * Create a sub span, and [enter] it, with the given values. + */ + fun <T> enterWith(vararg pairs: Pair<String, ContextValue>, block: Span.() -> T): T { + return enter().use { span -> + pairs.forEach { (k, value) -> + span.add(k, value) + } + block(span) + } + } + + /** + * Create a sub span, to attach some additional context, without modifying the [current] at all. + */ + fun forkWith(vararg pairs: Pair<String, ContextValue?>): Span { + val newSpan = fork() + for ((key, value) in pairs) { + if (value == null) continue + newSpan.add(key, value) + } + return newSpan + } + + /** + * Create a sub span, to which additional context can be added. This context will receive updates from its parent, + * and will be set as the [current]. To return to the parent, either call [exit] on the child. Or use inside of a + * [use] block. + */ + fun enter(): Span { + require(_current.get() == this) + return fork().enterSelf() + } + + /** + * Force [enter] this span, without creating a subspan. This bypasses checks like parent / child being the [current]. + */ + fun enterSelf(): Span { + _current.set(this) + return this + } + + /** + * Creates a temporary sub span, to which additional context can be added. This context will receive updates from + * its parent, but will not be set as the [current]. + */ + fun fork(): Span { + return Span(this) + } + + /** + * Create a new root span, that will not receive any updates from the current span, but will have all the same + * context keys associated. + */ + fun forkNewRoot(): Span { + val newRoot = Span(null) + newRoot.data.data.putAll(collectContext().data) + return newRoot + } + + /** + * Collect the context, including all parent context + */ + fun collectContext(): Context { + if (parent != null) + return data.combineWith(parent.collectContext()) + return data + } + + /** + * Exit an [entered][enter] span, returning back to the parent context, and discard any current keys. + */ + fun exit() { + require(parent != null) + require(_current.get() == this) + _current.set(parent) + } + + /** + * [AutoCloseable] implementation for [exit] + */ + override fun close() { + return exit() + } + + /** + * Record an empty event given the context. This indicates nothing except for "I was here". + * @see recordMessageEvent + * @see recordException + */ + fun recordEmptyTrace(recorder: EventRecorder) { + recorder.record(RecordedEvent(collectContext())) + } + + /** + * Record a message with the key `"event_message"` to the recorder + */ + fun recordMessageEvent( + recorder: EventRecorder, + message: String + ) { + forkWith(CommonKeys.EVENT_MESSAGE to ContextValue.string(message)) + .recordEmptyTrace(recorder) + } + + /** + * Record an exception to the recorder + */ + fun recordException( + recorder: EventRecorder, + exception: Throwable, + message: String? = null + ) { + forkWith( + CommonKeys.EVENT_MESSAGE to message?.let(ContextValue::string), + CommonKeys.EXCEPTION to ExceptionContextValue(exception), + ).recordEmptyTrace(recorder) + } + +}
\ No newline at end of file diff --git a/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/StringContext.kt b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/StringContext.kt new file mode 100644 index 0000000..2d33075 --- /dev/null +++ b/mod/src/main/kotlin/moe/nea/ledger/utils/telemetry/StringContext.kt @@ -0,0 +1,11 @@ +package moe.nea.ledger.utils.telemetry + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive + +class StringContext(val message: String) : ContextValue { + override fun serialize(): JsonElement { + return JsonPrimitive(message) + } + +} |