{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars/sdk_sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":["code-group"]},"type":"markdown"},"seo":{"title":"Logging and observability","description":"The specifications of Ratepay's APIs for easy payment transactions.","siteUrl":"https://docs.ratepay.com","keywords":"Ratepay developer portal, Ratepay api portal, Ratepay api reference docs","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"logging-and-observability","__idx":0},"children":["Logging and observability"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you need visibility into SDK calls, like request/response logging, latency metrics,"," ","trace spans, you can wire up a wrapper around ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RatepayClient"]}," that adds that behavior without modifying the SDK itself."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"wrapping-the-client-for-loggingmetrics","__idx":1},"children":["Wrapping the client for logging/metrics"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A logging/observability wrapper is the same pattern applied to production code instead of tests:"," ","wrap a real ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RatepayClient"]}," instance, delegate every call to it, and add your cross-cutting behavior"," ","around the delegated call."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RatepayClient"]}," declares many operations (authorize, capture, refund, getters, OTP, ...), and a Java class that"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["implements RatepayClient"]}," must override all of them. The simplest Java approach is a"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["java.lang.reflect.Proxy"]},", which logs every call without listing each method. Kotlin can use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["by delegate"]}," ","and override only what it needs."]},{"$$mdtype":"Tag","name":"CodeGroup","attributes":{"mode":"tabs"},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","data-title":"Java","header":{"title":"Java","controls":{"copy":{}}},"source":"import com.ratepay.sdk.*;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Proxy;\n\npublic final class LoggingRatepayClient {\n    private LoggingRatepayClient() {}\n\n    public static RatepayClient wrap(RatepayClient delegate) {\n        return (RatepayClient) Proxy.newProxyInstance(\n            RatepayClient.class.getClassLoader(),\n            new Class<?>[] {RatepayClient.class},\n            (proxy, method, args) -> {\n                long start = System.nanoTime();\n                try {\n                    Object result = method.invoke(delegate, args);\n                    log(method.getName(), \"OK\", start, null);\n                    return result;\n                } catch (InvocationTargetException e) {\n                    log(method.getName(), \"EXCEPTION\", start, e.getCause());\n                    throw e.getCause();\n                }\n            });\n    }\n\n    private static void log(String operation, String result, long startNanos, Throwable error) {\n        long elapsedMs = (System.nanoTime() - startNanos) / 1_000_000;\n        if (error != null) {\n            System.err.printf(\"ratepay.%s failed after %dms: %s%n\", operation, elapsedMs, error);\n        } else {\n            System.out.printf(\"ratepay.%s -> %s (%dms)%n\", operation, result, elapsedMs);\n        }\n    }\n}\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","data-title":"Kotlin","header":{"title":"Kotlin","controls":{"copy":{}}},"source":"import com.ratepay.sdk.*\nimport com.ratepay.sdk.exception.RatepayException\nimport com.ratepay.sdk.model.api.payment.*\n\nclass LoggingRatepayClient(private val delegate: RatepayClient) : RatepayClient by delegate {\n\n    override fun authorize(request: AuthorizationRequest, idempotencyKey: String?): AuthorizeOutcome {\n        val start = System.nanoTime()\n        try {\n            val outcome = delegate.authorize(request, idempotencyKey)\n            log(\"authorize\", outcome.kind().toString(), start)\n            return outcome\n        } catch (e: RatepayException) {\n            log(\"authorize\", \"EXCEPTION\", start, e)\n            throw e\n        }\n    }\n\n\n    private fun log(operation: String, result: String, startNanos: Long, error: Throwable? = null) {\n        val elapsedMs = (System.nanoTime() - startNanos) / 1_000_000\n        if (error != null) {\n            System.err.println(\"ratepay.$operation failed after ${elapsedMs}ms: $error\")\n        } else {\n            println(\"ratepay.$operation -> $result (${elapsedMs}ms)\")\n        }\n    }\n}\n","lang":"kotlin"},"children":[]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Swap in a real logging framework (SLF4J) or a metrics client (Micrometer) in place of the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["System.out"]},"/",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["System.err"]}," calls shown here."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"using-the-wrapper","__idx":2},"children":["Using the wrapper"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Build the wrapper once, in the same place you build the underlying client, and use the wrapped instance"," ","everywhere downstream code would otherwise use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["RatepayClient"]}," directly:"]},{"$$mdtype":"Tag","name":"CodeGroup","attributes":{"mode":"tabs"},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"java","data-title":"Java","header":{"title":"Java","controls":{"copy":{}}},"source":"RatepayClient client = LoggingRatepayClient.wrap(\n    RatepayClient.builder()\n        .clientId(System.getenv(\"RATEPAY_CLIENT_ID\"))\n        .clientSecret(System.getenv(\"RATEPAY_CLIENT_SECRET\"))\n        .environment(Environment.PRODUCTION)\n        .build()\n);\n","lang":"java"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"kotlin","data-title":"Kotlin","header":{"title":"Kotlin","controls":{"copy":{}}},"source":"val client: RatepayClient = LoggingRatepayClient(\n    RatepayClient {\n        clientId(System.getenv(\"RATEPAY_CLIENT_ID\"))\n        clientSecret(System.getenv(\"RATEPAY_CLIENT_SECRET\"))\n        environment(Environment.PRODUCTION)\n    },\n)\n","lang":"kotlin"},"children":[]}]}]},"headings":[{"value":"Logging and observability","id":"logging-and-observability","depth":1},{"value":"Wrapping the client for logging/metrics","id":"wrapping-the-client-for-loggingmetrics","depth":2},{"value":"Using the wrapper","id":"using-the-wrapper","depth":2}],"frontmatter":{"seo":{"title":"Logging and observability"}},"lastModified":"2026-08-17T10:04:52.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/developer/sdk/operations/logging-and-observability","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}