|
1 | 1 | package com.amazonaws.services.lambda.runtime.api.client.logging; |
2 | 2 |
|
3 | | -import com.amazonaws.lambda.thirdparty.org.json.JSONObject; |
4 | 3 | import com.amazonaws.services.lambda.runtime.api.client.api.LambdaContext; |
5 | 4 | import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer; |
6 | 5 | import com.amazonaws.services.lambda.runtime.serialization.factories.GsonFactory; |
7 | 6 | import org.junit.jupiter.api.Test; |
8 | 7 |
|
9 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; |
10 | | -import static org.junit.jupiter.api.Assertions.assertFalse; |
11 | 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; |
12 | | -import static org.junit.jupiter.api.Assertions.assertTrue; |
13 | 10 |
|
14 | 11 | import com.amazonaws.services.lambda.runtime.logging.LogLevel; |
15 | 12 |
|
16 | 13 | public class JsonLogFormatterTest { |
17 | 14 |
|
18 | | - /* |
19 | | - * Gson serialization-neutrality tests for the String -> Object type change of |
20 | | - * StructuredLogMessage.message. These pin the exact serialized behavior so any |
21 | | - * future regression (e.g. from a Gson upgrade changing runtime-type adapter |
22 | | - * resolution) is caught here rather than in customers' log pipelines. |
23 | | - */ |
24 | | - |
25 | | - @Test |
26 | | - void testStringMessageStillSerializesAsJsonStringWithObjectDeclaredField() { |
27 | | - JsonLogFormatter formatter = new JsonLogFormatter(); |
28 | | - String output = formatter.format("test log", LogLevel.INFO); |
29 | | - |
30 | | - JSONObject parsed = new JSONObject(output); |
31 | | - // message must be a JSON string value, not an object or anything else |
32 | | - assertTrue(parsed.get("message") instanceof String); |
33 | | - assertEquals("test log", parsed.getString("message")); |
34 | | - assertEquals("INFO", parsed.getString("level")); |
35 | | - } |
36 | | - |
37 | | - @Test |
38 | | - void testStringMessageEscapingUnchangedWithObjectDeclaredField() { |
39 | | - // quotes, backslashes, newlines, unicode and HTML-sensitive chars must |
40 | | - // round-trip exactly as before the field type change |
41 | | - String tricky = "quote\" backslash\\ newline\n tab\t unicode\u00e9 html<>&"; |
42 | | - JsonLogFormatter formatter = new JsonLogFormatter(); |
43 | | - String output = formatter.format(tricky, LogLevel.WARN); |
44 | | - |
45 | | - JSONObject parsed = new JSONObject(output); |
46 | | - assertEquals(tricky, parsed.getString("message")); |
47 | | - } |
48 | | - |
49 | | - @Test |
50 | | - void testNullStringMessageOmittedExactlyAsBefore() { |
51 | | - // serializeNulls(false) omitted a null String message before the change; |
52 | | - // a null Object message must behave identically |
53 | | - JsonLogFormatter formatter = new JsonLogFormatter(); |
54 | | - String output = formatter.format((String) null, LogLevel.INFO); |
55 | | - |
56 | | - JSONObject parsed = new JSONObject(output); |
57 | | - assertFalse(parsed.has("message")); |
58 | | - assertNotNull(parsed.getString("timestamp")); |
59 | | - } |
60 | | - |
61 | | - static class SampleStructuredEvent { |
62 | | - final String event = "sample_event"; |
63 | | - final int intValue; |
64 | | - final String textValue; |
65 | | - |
66 | | - SampleStructuredEvent(int intValue, String textValue) { |
67 | | - this.intValue = intValue; |
68 | | - this.textValue = textValue; |
69 | | - } |
70 | | - } |
71 | | - |
72 | | - @Test |
73 | | - void testObjectMessageSerializesAsNestedJsonObject() { |
74 | | - JsonLogFormatter formatter = new JsonLogFormatter(); |
75 | | - String output = formatter.format(new SampleStructuredEvent(42, "abc"), LogLevel.DEBUG); |
76 | | - |
77 | | - JSONObject parsed = new JSONObject(output); |
78 | | - JSONObject message = parsed.getJSONObject("message"); |
79 | | - assertEquals("sample_event", message.getString("event")); |
80 | | - assertEquals(42, message.getInt("intValue")); |
81 | | - assertEquals("abc", message.getString("textValue")); |
82 | | - assertEquals(3, message.length()); |
83 | | - assertEquals("DEBUG", parsed.getString("level")); |
84 | | - assertNotNull(parsed.getString("timestamp")); |
85 | | - } |
86 | | - |
87 | | - @Test |
88 | | - void testObjectMessageWithLambdaContextKeepsEnvelopeFields() { |
89 | | - JsonLogFormatter formatter = new JsonLogFormatter(); |
90 | | - formatter.setLambdaContext(new LambdaContext( |
91 | | - 0, 0, "request-id", null, null, "function-name", |
92 | | - null, null, "function-arn", "tenant-id", null, null)); |
93 | | - String output = formatter.format(new SampleStructuredEvent(1, "x"), LogLevel.DEBUG); |
94 | | - |
95 | | - JSONObject parsed = new JSONObject(output); |
96 | | - assertEquals("request-id", parsed.getString("AWSRequestId")); |
97 | | - assertEquals("tenant-id", parsed.getString("tenantId")); |
98 | | - assertEquals(1, parsed.getJSONObject("message").getInt("intValue")); |
99 | | - } |
100 | | - |
101 | | - @Test |
102 | | - void testReflectiveStringAccessToMessageFieldStillWorks() throws Exception { |
103 | | - // StructuredLogMessage is internal, but be conservative about reflective |
104 | | - // consumers: setting and reading a String through the field must not break. |
105 | | - StructuredLogMessage msg = new StructuredLogMessage(); |
106 | | - java.lang.reflect.Field field = StructuredLogMessage.class.getDeclaredField("message"); |
107 | | - field.set(msg, "reflective string"); |
108 | | - assertEquals("reflective string", (String) field.get(msg)); |
109 | | - |
110 | | - PojoSerializer<StructuredLogMessage> serializer = |
111 | | - GsonFactory.getInstance().getSerializer(StructuredLogMessage.class); |
112 | | - java.io.ByteArrayOutputStream stream = new java.io.ByteArrayOutputStream(); |
113 | | - serializer.toJson(msg, stream); |
114 | | - JSONObject parsed = new JSONObject(stream.toString("UTF-8")); |
115 | | - assertEquals("reflective string", parsed.getString("message")); |
116 | | - } |
117 | | - |
118 | | - @Test |
119 | | - void testStringMessageDeserializationRoundTripUnchanged() { |
120 | | - // fromJson of a string-message log line must still yield a String in the field |
121 | | - JsonLogFormatter formatter = new JsonLogFormatter(); |
122 | | - String output = formatter.format("round trip", LogLevel.INFO); |
123 | | - |
124 | | - PojoSerializer<StructuredLogMessage> serializer = |
125 | | - GsonFactory.getInstance().getSerializer(StructuredLogMessage.class); |
126 | | - StructuredLogMessage result = serializer.fromJson(output); |
127 | | - assertTrue(result.message instanceof String); |
128 | | - assertEquals("round trip", result.message); |
129 | | - } |
130 | | - |
131 | 15 | @Test |
132 | 16 | void testFormattingWithoutLambdaContext() { |
133 | 17 | assertFormatsString("test log", LogLevel.WARN, null); |
|
0 commit comments