Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions posthog/src/main/java/com/posthog/internal/PostHogApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ public class PostHogApi(
config.serializer.serialize(batch, it.bufferedWriter())
}

logRequestHeaders(request)

client.newCall(request).execute().use {
val response = logResponse(it)

Expand All @@ -85,6 +87,8 @@ public class PostHogApi(
config.serializer.serialize(events, it.bufferedWriter())
}

logRequestHeaders(request)

client.newCall(request).execute().use {
val response = logResponse(it)

Expand Down Expand Up @@ -141,6 +145,8 @@ public class PostHogApi(
config.serializer.serialize(flagsRequest, it.bufferedWriter())
}

logRequestHeaders(request)

client.newCall(request).execute().use {
val response = logResponse(it)

Expand Down Expand Up @@ -177,6 +183,8 @@ public class PostHogApi(
.get()
.build()

logRequestHeaders(request)

client.newCall(request).execute().use {
val response = logResponse(it)

Expand Down Expand Up @@ -223,6 +231,8 @@ public class PostHogApi(

val request = requestBuilder.get().build()

logRequestHeaders(request)

client.newCall(request).execute().use {
val response = logResponse(it)

Expand Down Expand Up @@ -290,4 +300,16 @@ public class PostHogApi(
}
}
}

private fun logRequestHeaders(request: Request) {
if (config.debug) {
try {
val headers = request.headers
val headerStrings = headers.names().map { name -> "$name: ${headers[name]}" }
config.logger.log("Request headers for ${request.url}: ${headerStrings.joinToString(", ")}")
} catch (e: Throwable) {
// ignore
}
}
}
}
130 changes: 130 additions & 0 deletions posthog/src/test/java/com/posthog/internal/PostHogApiTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,28 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue

internal class PostHogApiTest {
private class TestLogger : PostHogLogger {
val messages = mutableListOf<String>()

override fun log(message: String) {
messages.add(message)
}

override fun isEnabled(): Boolean = true
}

private fun getSut(
host: String,
proxy: Proxy? = null,
debug: Boolean = false,
logger: PostHogLogger? = null,
): PostHogApi {
val config = PostHogConfig(API_KEY, host)
config.proxy = proxy
config.debug = debug
if (logger != null) {
config.logger = logger
}
return PostHogApi(config)
}

Expand Down Expand Up @@ -310,4 +326,118 @@ internal class PostHogApiTest {
}
assertEquals(401, exc.statusCode)
}

// Debug Header Logging Tests

@Test
fun `batch logs request headers in debug mode`() {
val http = mockHttp()
val url = http.url("/")
val logger = TestLogger()

val sut = getSut(host = url.toString(), debug = true, logger = logger)

val event = generateEvent()
sut.batch(listOf(event))

assertTrue(
logger.messages.any { it.contains("Request headers for") && it.contains("/batch") },
"Should log request headers for /batch endpoint",
)
assertTrue(
logger.messages.any { it.contains("User-Agent:") },
"Should include User-Agent header in log",
)
}

@Test
fun `batch does not log headers when debug is disabled`() {
val http = mockHttp()
val url = http.url("/")
val logger = TestLogger()

val sut = getSut(host = url.toString(), debug = false, logger = logger)

val event = generateEvent()
sut.batch(listOf(event))

assertFalse(
logger.messages.any { it.contains("Request headers for") },
"Should not log request headers when debug is disabled",
)
}

@Test
fun `flags logs request headers in debug mode`() {
val file = File("src/test/resources/json/flags-v1/basic-flags-no-errors.json")
val responseFlagsApi = file.readText()

val http =
mockHttp(
response =
MockResponse()
.setBody(responseFlagsApi),
)
val url = http.url("/")
val logger = TestLogger()

val sut = getSut(host = url.toString(), debug = true, logger = logger)

sut.flags("distinctId", anonymousId = "anonId", emptyMap())

assertTrue(
logger.messages.any { it.contains("Request headers for") && it.contains("/flags") },
"Should log request headers for /flags endpoint",
)
}

@Test
fun `localEvaluation logs request headers including Authorization in debug mode`() {
val http =
mockHttp(
response =
MockResponse()
.setBody(createLocalEvaluationJson())
.setHeader("ETag", "\"test-etag\""),
)
val url = http.url("/")
val logger = TestLogger()

val sut = getSut(host = url.toString(), debug = true, logger = logger)

sut.localEvaluation("test-personal-key")

assertTrue(
logger.messages.any { it.contains("Request headers for") && it.contains("/local_evaluation") },
"Should log request headers for /local_evaluation endpoint",
)
assertTrue(
logger.messages.any { it.contains("Authorization:") },
"Should include Authorization header in log",
)
}

@Test
fun `remoteConfig logs request headers in debug mode`() {
val file = File("src/test/resources/json/basic-remote-config.json")
val responseApi = file.readText()

val http =
mockHttp(
response =
MockResponse()
.setBody(responseApi),
)
val url = http.url("/")
val logger = TestLogger()

val sut = getSut(host = url.toString(), debug = true, logger = logger)

sut.remoteConfig()

assertTrue(
logger.messages.any { it.contains("Request headers for") && it.contains("/array/") },
"Should log request headers for /array/ (remoteConfig) endpoint",
)
}
}