Skip to content

Commit 33217f9

Browse files
authored
Add support for AppServiceConsoleLogs (#53)
* Add objects for AppServiceConsoleLogsType * Add AppServiceConsoleLogsType to NLFPlugin.syslogMessage * Make variables immutable in AppServiceConsoleLogsType * Run Spotless:apply * Fix a method name in AppServiceConsoleLogsType.appName * Run Spotless:apply
1 parent c27d79e commit 33217f9

8 files changed

Lines changed: 604 additions & 35 deletions

File tree

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ public List<SyslogMessage> syslogMessage(final ParsedEvent parsedEvent) throws P
103103
else if (jsonObject.getString("Type").equals("ADFPipelineRun")) {
104104
eventTypes.add(new ADFPipelineRunType(parsedEvent, realHostname));
105105
}
106+
else if (jsonObject.getString("Type").equals("AppServiceConsoleLogs")) {
107+
eventTypes.add(new AppServiceConsoleLogsType(parsedEvent, realHostname));
108+
}
106109
else if (jsonObject.getString("Type").equals("AppTraces")) {
107110
eventTypes.add(new AppInsightType(parsedEvent, realHostname));
108111
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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.ASCIIString;
52+
import com.teragrep.nlf_01.util.MD5Hash;
53+
import com.teragrep.nlf_01.util.ResourceId;
54+
import com.teragrep.nlf_01.util.ValidRFC5424AppName;
55+
import com.teragrep.nlf_01.util.ValidRFC5424Hostname;
56+
import com.teragrep.nlf_01.util.ValidRFC5424Timestamp;
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 jakarta.json.JsonValue;
62+
import java.time.Instant;
63+
import java.util.HashSet;
64+
import java.util.Set;
65+
import java.util.UUID;
66+
67+
public final class AppServiceConsoleLogsType implements EventType {
68+
69+
private final ParsedEvent parsedEvent;
70+
private final String realHostname;
71+
72+
public AppServiceConsoleLogsType(final ParsedEvent parsedEvent, final String realHostname) {
73+
this.parsedEvent = parsedEvent;
74+
this.realHostname = realHostname;
75+
}
76+
77+
private void assertKey(final JsonObject obj, final String key, final JsonValue.ValueType type)
78+
throws PluginException {
79+
if (!obj.containsKey(key)) {
80+
throw new PluginException(new IllegalArgumentException("Key " + key + " does not exist"));
81+
}
82+
83+
if (!obj.get(key).getValueType().equals(type)) {
84+
throw new PluginException(new IllegalArgumentException("Key " + key + " is not of type " + type));
85+
}
86+
}
87+
88+
@Override
89+
public Severity severity() {
90+
return Severity.NOTICE;
91+
}
92+
93+
@Override
94+
public Facility facility() {
95+
return Facility.AUDIT;
96+
}
97+
98+
@Override
99+
public String hostname() throws PluginException {
100+
final JsonObject record = parsedEvent.asJsonStructure().asJsonObject();
101+
102+
assertKey(record, "_ResourceId", JsonValue.ValueType.STRING);
103+
final String resourceId = record.getString("_ResourceId");
104+
105+
return new ValidRFC5424Hostname(
106+
"md5-".concat(new MD5Hash(resourceId).md5().concat("-").concat(new ASCIIString(new ResourceId(resourceId).resourceName()).withNonAsciiCharsRemoved()))
107+
).hostnameWithInvalidCharsRemoved();
108+
}
109+
110+
@Override
111+
public String appName() throws PluginException {
112+
final JsonObject record = parsedEvent.asJsonStructure().asJsonObject();
113+
114+
assertKey(record, "Type", JsonValue.ValueType.STRING);
115+
116+
return new ValidRFC5424AppName(record.getString("Type")).appName();
117+
}
118+
119+
@Override
120+
public long timestamp() throws PluginException {
121+
final JsonObject record = parsedEvent.asJsonStructure().asJsonObject();
122+
123+
assertKey(record, "TimeGenerated", JsonValue.ValueType.STRING);
124+
125+
return new ValidRFC5424Timestamp(record.getString("TimeGenerated")).validTimestamp();
126+
}
127+
128+
@Override
129+
public Set<SDElement> sdElements() throws PluginException {
130+
final Set<SDElement> elems = new HashSet<>();
131+
final String time;
132+
if (!parsedEvent.enqueuedTimeUtc().isStub()) {
133+
time = parsedEvent.enqueuedTimeUtc().zonedDateTime().toString();
134+
}
135+
else {
136+
time = "";
137+
}
138+
139+
final String fullyQualifiedNamespace;
140+
final String eventHubName;
141+
final String partitionId;
142+
final String consumerGroup;
143+
if (!parsedEvent.partitionCtx().isStub()) {
144+
fullyQualifiedNamespace = String
145+
.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("FullyQualifiedNamespace", ""));
146+
eventHubName = String.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("EventHubName", ""));
147+
partitionId = String.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("PartitionId", ""));
148+
consumerGroup = String.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("ConsumerGroup", ""));
149+
}
150+
else {
151+
fullyQualifiedNamespace = "";
152+
eventHubName = "";
153+
partitionId = "";
154+
consumerGroup = "";
155+
}
156+
157+
elems
158+
.add(new SDElement("aer_02_partition@48577").addSDParam("fully_qualified_namespace", fullyQualifiedNamespace).addSDParam("eventhub_name", eventHubName).addSDParam("partition_id", partitionId).addSDParam("consumer_group", consumerGroup));
159+
160+
elems
161+
.add(new SDElement("event_id@48577").addSDParam("uuid", UUID.randomUUID().toString()).addSDParam("hostname", realHostname).addSDParam("unixtime", Instant.now().toString()).addSDParam("id_source", "aer_02"));
162+
163+
final String partitionKey;
164+
if (!parsedEvent.systemProperties().isStub()) {
165+
partitionKey = String.valueOf(parsedEvent.systemProperties().asMap().getOrDefault("PartitionKey", ""));
166+
}
167+
else {
168+
partitionKey = "";
169+
}
170+
171+
final String offset;
172+
if (!parsedEvent.offset().isStub()) {
173+
offset = parsedEvent.offset().value();
174+
}
175+
else {
176+
offset = "";
177+
}
178+
179+
elems
180+
.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()));
181+
182+
elems
183+
.add(new SDElement("aer_02@48577").addSDParam("timestamp_source", time.isEmpty() ? "generated" : "timeEnqueued"));
184+
185+
elems.add(new SDElement("nlf_01@48577").addSDParam("eventType", this.getClass().getSimpleName()));
186+
187+
return elems;
188+
}
189+
190+
@Override
191+
public String msgId() throws PluginException {
192+
final String sequenceNumber;
193+
if (!parsedEvent.systemProperties().isStub()) {
194+
sequenceNumber = String.valueOf(parsedEvent.systemProperties().asMap().getOrDefault("SequenceNumber", ""));
195+
}
196+
else {
197+
sequenceNumber = "";
198+
}
199+
return sequenceNumber;
200+
}
201+
202+
@Override
203+
public String msg() throws PluginException {
204+
return parsedEvent.asString();
205+
}
206+
}

src/main/java/com/teragrep/nlf_01/types/ContainerAppConsoleLogsType.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,12 @@ public String appName() throws PluginException {
120120
if (record.containsKey("ContainerAppName")) {
121121
assertKey(record, "ContainerAppName", JsonValue.ValueType.STRING);
122122
keyValue = record.getString("ContainerAppName");
123-
} else if (record.containsKey("JobName")) {
123+
}
124+
else if (record.containsKey("JobName")) {
124125
assertKey(record, "JobName", JsonValue.ValueType.STRING);
125126
keyValue = record.getString("JobName");
126-
} else {
127+
}
128+
else {
127129
throw new PluginException(new IllegalArgumentException("A valid key does not exist"));
128130
}
129131

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

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,12 @@ void adfActivityRunType() {
414414
@Test
415415
void containerAppConsoleLogsTypeWithContainerAppName() {
416416
final String json = Assertions
417-
.assertDoesNotThrow(() -> Files.readString(Paths.get("src/test/resources/containerappconsolelogswithcontainerappname.json")));
417+
.assertDoesNotThrow(
418+
() -> Files
419+
.readString(
420+
Paths.get("src/test/resources/containerappconsolelogswithcontainerappname.json")
421+
)
422+
);
418423
final ParsedEvent parsedEvent = new ParsedEventFactory(
419424
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"))
420425
).parsedEvent();
@@ -445,14 +450,16 @@ void containerAppConsoleLogsTypeWithContainerAppName() {
445450
@Test
446451
void containerAppConsoleLogsTypeWithJobName() {
447452
final String json = Assertions
448-
.assertDoesNotThrow(() -> Files.readString(Paths.get("src/test/resources/containerappconsolelogswithjobname.json")));
453+
.assertDoesNotThrow(
454+
() -> Files.readString(Paths.get("src/test/resources/containerappconsolelogswithjobname.json"))
455+
);
449456
final ParsedEvent parsedEvent = new ParsedEventFactory(
450-
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"))
457+
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"))
451458
).parsedEvent();
452459

453460
final NLFPlugin plugin = new NLFPlugin(new FakeSourceable());
454461
final List<SyslogMessage> syslogMessages = Assertions
455-
.assertDoesNotThrow(() -> plugin.syslogMessage(parsedEvent));
462+
.assertDoesNotThrow(() -> plugin.syslogMessage(parsedEvent));
456463
Assertions.assertEquals(1, syslogMessages.size());
457464

458465
final SyslogMessage syslogMessage = syslogMessages.get(0);
@@ -461,18 +468,62 @@ void containerAppConsoleLogsTypeWithJobName() {
461468
Assertions.assertEquals("2020-01-01T01:23:34.567Z", syslogMessage.getTimestamp());
462469
Assertions.assertEquals(json, syslogMessage.getMsg());
463470
final Map<String, Map<String, String>> sdElementMap = syslogMessage
464-
.getSDElements()
465-
.stream()
466-
.collect(Collectors.toMap((SDElement::getSdID), (sdElem) -> sdElem.getSdParams().stream().collect(Collectors.toMap(SDParam::getParamName, SDParam::getParamValue))));
471+
.getSDElements()
472+
.stream()
473+
.collect(Collectors.toMap((SDElement::getSdID), (sdElem) -> sdElem.getSdParams().stream().collect(Collectors.toMap(SDParam::getParamName, SDParam::getParamValue))));
467474

468475
Assertions.assertEquals(1, sdElementMap.get("nlf_01@48577").size());
469476
Assertions
470-
.assertEquals(ContainerAppConsoleLogsType.class.getSimpleName(), sdElementMap.get("nlf_01@48577").get("eventType"));
477+
.assertEquals(ContainerAppConsoleLogsType.class.getSimpleName(), sdElementMap.get("nlf_01@48577").get("eventType"));
471478

472479
Assertions.assertEquals(4, sdElementMap.get("aer_02_event@48577").size());
473480
Assertions.assertTrue(sdElementMap.get("aer_02_event@48577").containsKey("properties"));
474481
}
475482

483+
@Test
484+
void appServiceConsoleLogsType() {
485+
final String json = Assertions
486+
.assertDoesNotThrow(() -> Files.readString(Paths.get("src/test/resources/appserviceconsolelogs.json")));
487+
final ParsedEvent parsedEvent = new ParsedEventFactory(
488+
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"))
489+
).parsedEvent();
490+
491+
final NLFPlugin plugin = new NLFPlugin(new FakeSourceable());
492+
final List<SyslogMessage> syslogMessages = Assertions
493+
.assertDoesNotThrow(() -> plugin.syslogMessage(parsedEvent));
494+
Assertions.assertEquals(1, syslogMessages.size());
495+
496+
final SyslogMessage syslogMessage = syslogMessages.get(0);
497+
Assertions
498+
.assertEquals(
499+
"{\n" + " \"Category\": \"app-service-console-logs\",\n"
500+
+ " \"ContainerId\": \"container-id\",\n" + " \"Host\": \"host-1\",\n"
501+
+ " \"Level\": \"Debug\",\n" + " \"OperationName\": \"operation-1\",\n"
502+
+ " \"ResultDescription\": \"description\",\n" + " \"SourceSystem\": \"Azure\",\n"
503+
+ " \"TenantId\": \"12\",\n"
504+
+ " \"TimeGenerated\": \"2020-01-01T01:02:34.5678999Z\",\n"
505+
+ " \"Type\": \"AppServiceConsoleLogs\",\n" + " \"_BilledSize\": 1,\n"
506+
+ " \"_Internal_WorkspaceResourceId\": \"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}\",\n"
507+
+ " \"_ResourceId\": \"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}\"\n"
508+
+ "}",
509+
syslogMessage.getMsg()
510+
);
511+
Assertions.assertEquals("md5-0ded52ef915af563e25778bf26b0f129-resourceName", syslogMessage.getHostname());
512+
Assertions.assertEquals("AppServiceConsoleLogs", syslogMessage.getAppName());
513+
Assertions.assertEquals("2020-01-01T01:02:34.567Z", syslogMessage.getTimestamp());
514+
515+
final Map<String, Map<String, String>> sdElementMap = syslogMessage
516+
.getSDElements()
517+
.stream()
518+
.collect(Collectors.toMap((SDElement::getSdID), (sdElem) -> sdElem.getSdParams().stream().collect(Collectors.toMap(SDParam::getParamName, SDParam::getParamValue))));
519+
520+
Assertions.assertEquals(1, sdElementMap.get("nlf_01@48577").size());
521+
Assertions
522+
.assertEquals(AppServiceConsoleLogsType.class.getSimpleName(), sdElementMap.get("nlf_01@48577").get("eventType"));
523+
524+
Assertions.assertTrue(sdElementMap.get("aer_02_event@48577").containsKey("properties"));
525+
}
526+
476527
@Test
477528
void functionAppLogsType() {
478529
final String json = Assertions

0 commit comments

Comments
 (0)