Skip to content

Commit 3a62835

Browse files
authored
Add support for Istio Ingress events (#70)
* Implement IstioIngressContainerType and unit tests for it * Implement IstioIngressContainerType to NLFPlugin.syslogMessage and test it * Change NLFPlugin.syslogMessage logic for Istio events, where the podNamespace should equal "aks-istio-ingress" instead of containing it * Make the static appname non-static for IstioIngressContainerType
1 parent 2866def commit 3a62835

6 files changed

Lines changed: 596 additions & 9 deletions

File tree

src/main/java/com/teragrep/nlf_01/NLFPlugin.java

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,23 @@ else if (jsonObject.getString("Type").endsWith("_CL")) {
137137
eventTypes.add(new CLType(parsedEvent, realHostname));
138138
}
139139
else if (jsonObject.getString("Type").equals("ContainerLogV2")) {
140-
eventTypes
141-
.add(
142-
new ContainerType(
143-
parsedEvent,
144-
containerLogHostnameKey,
145-
containerLogAppNameKey,
146-
realHostname
147-
)
148-
);
140+
if (
141+
jsonObject.containsKey("PodNamespace")
142+
&& jsonObject.getString("PodNamespace").equals("aks-istio-ingress")
143+
) {
144+
eventTypes.add(new IstioIngressContainerType(parsedEvent, realHostname));
145+
}
146+
else {
147+
eventTypes
148+
.add(
149+
new ContainerType(
150+
parsedEvent,
151+
containerLogHostnameKey,
152+
containerLogAppNameKey,
153+
realHostname
154+
)
155+
);
156+
}
149157
}
150158
else if (jsonObject.getString("Type").equals("Syslog")) {
151159
eventTypes.add(new SyslogType(parsedEvent, syslogExpectedProcessName, realHostname));
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* Teragrep Neon log format plugin for AKV_01
3+
* Copyright (C) 2025 Suomen Kanuuna Oy
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*
18+
*
19+
* Additional permission under GNU Affero General Public License version 3
20+
* section 7
21+
*
22+
* If you modify this Program, or any covered work, by linking or combining it
23+
* with other code, such other code is not for that reason alone subject to any
24+
* of the requirements of the GNU Affero GPL version 3 as long as this Program
25+
* is the same Program as licensed from Suomen Kanuuna Oy without any additional
26+
* modifications.
27+
*
28+
* Supplemented terms under GNU Affero General Public License version 3
29+
* section 7
30+
*
31+
* Origin of the software must be attributed to Suomen Kanuuna Oy. Any modified
32+
* versions must be marked as "Modified version of" The Program.
33+
*
34+
* Names of the licensors and authors may not be used for publicity purposes.
35+
*
36+
* No rights are granted for use of trade names, trademarks, or service marks
37+
* which are in The Program if any.
38+
*
39+
* Licensee must indemnify licensors and authors for any liability that these
40+
* contractual assumptions impose on licensors and authors.
41+
*
42+
* To the extent this program is licensed as part of the Commercial versions of
43+
* Teragrep, the applicable Commercial License may apply to this file if you as
44+
* a licensee so wish it.
45+
*/
46+
package com.teragrep.nlf_01.types;
47+
48+
import com.teragrep.akv_01.event.ParsedEvent;
49+
import com.teragrep.akv_01.plugin.PluginException;
50+
import com.teragrep.nlf_01.PropertiesJson;
51+
import com.teragrep.nlf_01.util.ResourceId;
52+
import com.teragrep.nlf_01.util.ValidKey;
53+
import com.teragrep.nlf_01.util.ValidRFC5424AppName;
54+
import com.teragrep.nlf_01.util.ValidRFC5424Hostname;
55+
import com.teragrep.nlf_01.util.ValidRFC5424Timestamp;
56+
import com.teragrep.nlf_01.util.ValidStringKey;
57+
import com.teragrep.rlo_14.Facility;
58+
import com.teragrep.rlo_14.SDElement;
59+
import com.teragrep.rlo_14.Severity;
60+
import jakarta.json.JsonObject;
61+
import java.time.Instant;
62+
import java.util.HashSet;
63+
import java.util.Set;
64+
import java.util.UUID;
65+
66+
public final class IstioIngressContainerType implements EventType {
67+
68+
private final String staticAppname;
69+
private final ParsedEvent parsedEvent;
70+
private final String realHostname;
71+
72+
public IstioIngressContainerType(final ParsedEvent parsedEvent, final String realHostname) {
73+
this("istio-ingress", parsedEvent, realHostname);
74+
}
75+
76+
private IstioIngressContainerType(
77+
final String staticAppname,
78+
final ParsedEvent parsedEvent,
79+
final String realHostname
80+
) {
81+
this.staticAppname = staticAppname;
82+
this.parsedEvent = parsedEvent;
83+
this.realHostname = realHostname;
84+
}
85+
86+
@Override
87+
public Severity severity() {
88+
return Severity.NOTICE;
89+
}
90+
91+
@Override
92+
public Facility facility() {
93+
return Facility.AUDIT;
94+
}
95+
96+
@Override
97+
public String hostname() throws PluginException {
98+
final JsonObject mainObject = parsedEvent.asJsonStructure().asJsonObject();
99+
100+
final ValidKey<String> containerLogHostnameKeyValidKey = new ValidStringKey(mainObject, "PodNamespace");
101+
102+
return new ValidRFC5424Hostname(containerLogHostnameKeyValidKey.value()).validHostname();
103+
}
104+
105+
@Override
106+
public String appName() throws PluginException {
107+
return new ValidRFC5424AppName(staticAppname).appName();
108+
}
109+
110+
@Override
111+
public long timestamp() throws PluginException {
112+
final JsonObject mainObject = parsedEvent.asJsonStructure().asJsonObject();
113+
114+
return new ValidRFC5424Timestamp(new ValidStringKey(mainObject, "TimeGenerated").value()).validTimestamp();
115+
}
116+
117+
@Override
118+
public Set<SDElement> sdElements() throws PluginException {
119+
final Set<SDElement> elems = new HashSet<>();
120+
final String time;
121+
if (!parsedEvent.enqueuedTimeUtc().isStub()) {
122+
time = parsedEvent.enqueuedTimeUtc().zonedDateTime().toString();
123+
}
124+
else {
125+
time = "";
126+
}
127+
128+
final String fullyQualifiedNamespace;
129+
final String eventHubName;
130+
final String partitionId;
131+
final String consumerGroup;
132+
if (!parsedEvent.partitionCtx().isStub()) {
133+
fullyQualifiedNamespace = String
134+
.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("FullyQualifiedNamespace", ""));
135+
eventHubName = String.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("EventHubName", ""));
136+
partitionId = String.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("PartitionId", ""));
137+
consumerGroup = String.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("ConsumerGroup", ""));
138+
}
139+
else {
140+
fullyQualifiedNamespace = "";
141+
eventHubName = "";
142+
partitionId = "";
143+
consumerGroup = "";
144+
}
145+
146+
elems
147+
.add(new SDElement("aer_02_partition@48577").addSDParam("fully_qualified_namespace", fullyQualifiedNamespace).addSDParam("eventhub_name", eventHubName).addSDParam("partition_id", partitionId).addSDParam("consumer_group", consumerGroup));
148+
149+
elems
150+
.add(new SDElement("event_id@48577").addSDParam("uuid", UUID.randomUUID().toString()).addSDParam("hostname", realHostname).addSDParam("unixtime", Instant.now().toString()).addSDParam("id_source", "aer_02"));
151+
152+
final String partitionKey;
153+
if (!parsedEvent.systemProperties().isStub()) {
154+
partitionKey = String.valueOf(parsedEvent.systemProperties().asMap().getOrDefault("PartitionKey", ""));
155+
}
156+
else {
157+
partitionKey = "";
158+
}
159+
160+
final String offset;
161+
if (!parsedEvent.offset().isStub()) {
162+
offset = parsedEvent.offset().value();
163+
}
164+
else {
165+
offset = "";
166+
}
167+
168+
elems
169+
.add(new SDElement("aer_02_event@48577").addSDParam("offset", offset).addSDParam("enqueued_time", time).addSDParam("partition_key", partitionKey).addSDParam("properties", new PropertiesJson(parsedEvent.properties()).toJsonObject().toString()));
170+
171+
elems
172+
.add(new SDElement("aer_02@48577").addSDParam("timestamp_source", time.isEmpty() ? "generated" : "timeEnqueued"));
173+
174+
final JsonObject mainObject = parsedEvent.asJsonStructure().asJsonObject();
175+
176+
final ValidKey<String> resourceIdValidKey = new ValidStringKey(mainObject, "_ResourceId");
177+
final ResourceId resourceId = new ResourceId(resourceIdValidKey.value());
178+
final String subscriptionId = resourceId.subscriptionId();
179+
final String clusterName = resourceId.resourceName();
180+
181+
final ValidKey<String> podNameValidKey = new ValidStringKey(mainObject, "PodName");
182+
final String podName = podNameValidKey.value();
183+
184+
final ValidKey<String> podNamespaceValidKey = new ValidStringKey(mainObject, "PodNamespace");
185+
final String podNamespace = podNamespaceValidKey.value();
186+
187+
final ValidKey<String> containerIdValidKey = new ValidStringKey(mainObject, "ContainerId");
188+
final String containerId = containerIdValidKey.value();
189+
190+
elems
191+
.add(new SDElement("origin@48577").addSDParam("subscription", subscriptionId).addSDParam("clusterName", clusterName).addSDParam("namespace", podNamespace).addSDParam("pod", podName).addSDParam("containerId", containerId));
192+
193+
elems.add(new SDElement("nlf_01@48577").addSDParam("eventType", this.getClass().getSimpleName()));
194+
195+
return elems;
196+
}
197+
198+
@Override
199+
public String msgId() {
200+
final String sequenceNumber;
201+
if (!parsedEvent.systemProperties().isStub()) {
202+
sequenceNumber = String.valueOf(parsedEvent.systemProperties().asMap().getOrDefault("SequenceNumber", ""));
203+
}
204+
else {
205+
sequenceNumber = "";
206+
}
207+
return sequenceNumber;
208+
}
209+
210+
@Override
211+
public String msg() {
212+
return parsedEvent.asString();
213+
}
214+
}

src/test/java/com/teragrep/nlf_01/NLFPluginTest.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,47 @@ void containerType() {
115115
Assertions.assertEquals("2020-01-01T00:00Z", sdElementMap.get("aer_02_event@48577").get("enqueued_time"));
116116
}
117117

118+
@Test
119+
void istioContainerType() {
120+
final String json = Assertions
121+
.assertDoesNotThrow(() -> Files.readString(Paths.get("src/test/resources/istiocontainer.json")));
122+
final ParsedEvent parsedEvent = new ParsedEventFactory(
123+
new UnparsedEventImpl(json, new EventPartitionContextImpl(new HashMap<>()), new EventPropertiesImpl(new HashMap<>()), new EventSystemPropertiesImpl(new HashMap<>()), new EnqueuedTimeImpl("2020-01-01T00:00:00"), new EventOffsetImpl("0"))
124+
).parsedEvent();
125+
126+
final NLFPlugin plugin = new NLFPlugin(new FakeSourceable());
127+
final List<SyslogMessage> syslogMessages = Assertions
128+
.assertDoesNotThrow(() -> plugin.syslogMessage(parsedEvent));
129+
Assertions.assertEquals(1, syslogMessages.size());
130+
131+
final SyslogMessage syslogMessage = syslogMessages.get(0);
132+
Assertions.assertEquals(json, syslogMessage.getMsg());
133+
Assertions.assertEquals("aks-istio-ingress", syslogMessage.getHostname());
134+
Assertions.assertEquals("istio-ingress", syslogMessage.getAppName());
135+
Assertions.assertEquals("2020-01-01T01:23:34.567Z", syslogMessage.getTimestamp());
136+
137+
final Map<String, Map<String, String>> sdElementMap = syslogMessage
138+
.getSDElements()
139+
.stream()
140+
.collect(Collectors.toMap((SDElement::getSdID), (sdElem) -> sdElem.getSdParams().stream().collect(Collectors.toMap(SDParam::getParamName, SDParam::getParamValue))));
141+
142+
Assertions.assertEquals(5, sdElementMap.get("origin@48577").size());
143+
Assertions.assertEquals("{subscriptionId}", sdElementMap.get("origin@48577").get("subscription"));
144+
Assertions.assertEquals("{resourceName}", sdElementMap.get("origin@48577").get("clusterName"));
145+
Assertions.assertEquals("aks-istio-ingress", sdElementMap.get("origin@48577").get("namespace"));
146+
Assertions.assertEquals("pod-name", sdElementMap.get("origin@48577").get("pod"));
147+
Assertions.assertEquals("container-id", sdElementMap.get("origin@48577").get("containerId"));
148+
149+
Assertions.assertEquals(1, sdElementMap.get("nlf_01@48577").size());
150+
Assertions
151+
.assertEquals(IstioIngressContainerType.class.getSimpleName(), sdElementMap.get("nlf_01@48577").get("eventType"));
152+
153+
Assertions.assertTrue(sdElementMap.get("aer_02_event@48577").containsKey("properties"));
154+
155+
Assertions.assertEquals("timeEnqueued", sdElementMap.get("aer_02@48577").get("timestamp_source"));
156+
Assertions.assertEquals("2020-01-01T00:00Z", sdElementMap.get("aer_02_event@48577").get("enqueued_time"));
157+
}
158+
118159
@Test
119160
void containerTypeWithMissingEnvVariables() {
120161
final String json = Assertions

0 commit comments

Comments
 (0)