Skip to content

Commit 92c3f8c

Browse files
authored
Add DataverseActivity support (#65)
* DataverseActivityType and unit tests for it * Add DataverseActivityType support to NLFPlugin.syslogMessage
1 parent 3440414 commit 92c3f8c

6 files changed

Lines changed: 622 additions & 0 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
@@ -112,6 +112,9 @@ else if (jsonObject.getString("Type").equals("AppTraces")) {
112112
else if (jsonObject.getString("Type").equals("ContainerAppConsoleLogs")) {
113113
eventTypes.add(new ContainerAppConsoleLogsType(parsedEvent, realHostname));
114114
}
115+
else if (jsonObject.getString("Type").equals("DataverseActivity")) {
116+
eventTypes.add(new DataverseActivityType(parsedEvent, realHostname));
117+
}
115118
else if (jsonObject.getString("Type").equals("FunctionAppLogs")) {
116119
eventTypes.add(new FunctionAppLogsType(parsedEvent, realHostname));
117120
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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.ValidKey;
55+
import com.teragrep.nlf_01.util.ValidRFC5424AppName;
56+
import com.teragrep.nlf_01.util.ValidRFC5424Hostname;
57+
import com.teragrep.nlf_01.util.ValidRFC5424Timestamp;
58+
import com.teragrep.nlf_01.util.ValidStringKey;
59+
import com.teragrep.rlo_14.Facility;
60+
import com.teragrep.rlo_14.SDElement;
61+
import com.teragrep.rlo_14.Severity;
62+
import jakarta.json.JsonObject;
63+
import java.time.Instant;
64+
import java.util.HashSet;
65+
import java.util.Set;
66+
import java.util.UUID;
67+
68+
public final class DataverseActivityType implements EventType {
69+
70+
private final ParsedEvent parsedEvent;
71+
private final String realHostname;
72+
73+
public DataverseActivityType(final ParsedEvent parsedEvent, final String realHostname) {
74+
this.parsedEvent = parsedEvent;
75+
this.realHostname = realHostname;
76+
}
77+
78+
@Override
79+
public Severity severity() {
80+
return Severity.NOTICE;
81+
}
82+
83+
@Override
84+
public Facility facility() {
85+
return Facility.AUDIT;
86+
}
87+
88+
@Override
89+
public String hostname() throws PluginException {
90+
final JsonObject record = parsedEvent.asJsonStructure().asJsonObject();
91+
92+
final ValidKey<String> validKey = new ValidStringKey(record, "_Internal_WorkspaceResourceId");
93+
94+
final String resourceId = validKey.value();
95+
96+
return new ValidRFC5424Hostname(
97+
"md5-".concat(new MD5Hash(resourceId).md5().concat("-").concat(new ASCIIString(new ResourceId(resourceId).resourceName()).withNonAsciiCharsRemoved()))
98+
).hostnameWithInvalidCharsRemoved();
99+
100+
}
101+
102+
@Override
103+
public String appName() throws PluginException {
104+
final JsonObject record = parsedEvent.asJsonStructure().asJsonObject();
105+
106+
final ValidKey<String> validKey = new ValidStringKey(record, "ItemUrl");
107+
108+
// Prepend 'DataverseA_' before the actual ItemUrl. A standing for Activity
109+
return new ValidRFC5424AppName(new ASCIIString("DataverseA_" + validKey.value()).withNonAsciiCharsRemoved())
110+
.appName();
111+
}
112+
113+
@Override
114+
public long timestamp() throws PluginException {
115+
final JsonObject record = parsedEvent.asJsonStructure().asJsonObject();
116+
117+
final ValidKey<String> validKey = new ValidStringKey(record, "TimeGenerated");
118+
119+
return new ValidRFC5424Timestamp(validKey.value()).validTimestamp();
120+
}
121+
122+
@Override
123+
public Set<SDElement> sdElements() throws PluginException {
124+
final Set<SDElement> elems = new HashSet<>();
125+
final String time;
126+
if (!parsedEvent.enqueuedTimeUtc().isStub()) {
127+
time = parsedEvent.enqueuedTimeUtc().zonedDateTime().toString();
128+
}
129+
else {
130+
time = "";
131+
}
132+
133+
final String fullyQualifiedNamespace;
134+
final String eventHubName;
135+
final String partitionId;
136+
final String consumerGroup;
137+
if (!parsedEvent.partitionCtx().isStub()) {
138+
fullyQualifiedNamespace = String
139+
.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("FullyQualifiedNamespace", ""));
140+
eventHubName = String.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("EventHubName", ""));
141+
partitionId = String.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("PartitionId", ""));
142+
consumerGroup = String.valueOf(parsedEvent.partitionCtx().asMap().getOrDefault("ConsumerGroup", ""));
143+
}
144+
else {
145+
fullyQualifiedNamespace = "";
146+
eventHubName = "";
147+
partitionId = "";
148+
consumerGroup = "";
149+
}
150+
151+
elems
152+
.add(new SDElement("aer_02_partition@48577").addSDParam("fully_qualified_namespace", fullyQualifiedNamespace).addSDParam("eventhub_name", eventHubName).addSDParam("partition_id", partitionId).addSDParam("consumer_group", consumerGroup));
153+
154+
elems
155+
.add(new SDElement("event_id@48577").addSDParam("uuid", UUID.randomUUID().toString()).addSDParam("hostname", realHostname).addSDParam("unixtime", Instant.now().toString()).addSDParam("id_source", "aer_02"));
156+
157+
final String partitionKey;
158+
if (!parsedEvent.systemProperties().isStub()) {
159+
partitionKey = String.valueOf(parsedEvent.systemProperties().asMap().getOrDefault("PartitionKey", ""));
160+
}
161+
else {
162+
partitionKey = "";
163+
}
164+
165+
final String offset;
166+
if (!parsedEvent.offset().isStub()) {
167+
offset = parsedEvent.offset().value();
168+
}
169+
else {
170+
offset = "";
171+
}
172+
173+
elems
174+
.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()));
175+
176+
elems
177+
.add(new SDElement("aer_02@48577").addSDParam("timestamp_source", time.isEmpty() ? "generated" : "timeEnqueued"));
178+
179+
elems.add(new SDElement("nlf_01@48577").addSDParam("eventType", this.getClass().getSimpleName()));
180+
181+
return elems;
182+
}
183+
184+
@Override
185+
public String msgId() throws PluginException {
186+
final String sequenceNumber;
187+
if (!parsedEvent.systemProperties().isStub()) {
188+
sequenceNumber = String.valueOf(parsedEvent.systemProperties().asMap().getOrDefault("SequenceNumber", ""));
189+
}
190+
else {
191+
sequenceNumber = "";
192+
}
193+
return sequenceNumber;
194+
}
195+
196+
@Override
197+
public String msg() throws PluginException {
198+
return parsedEvent.asString();
199+
}
200+
}

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,70 @@ void syslogType() {
380380
Assertions.assertTrue(sdElementMap.get("aer_02_event@48577").containsKey("properties"));
381381
}
382382

383+
@Test
384+
void dataverseActivityType() {
385+
final String json = Assertions
386+
.assertDoesNotThrow(() -> Files.readString(Paths.get("src/test/resources/dataverseactivity.json")));
387+
final ParsedEvent parsedEvent = new ParsedEventFactory(
388+
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"))
389+
).parsedEvent();
390+
391+
final NLFPlugin plugin = new NLFPlugin(new FakeSourceable());
392+
final List<SyslogMessage> syslogMessages = Assertions
393+
.assertDoesNotThrow(() -> plugin.syslogMessage(parsedEvent));
394+
Assertions.assertEquals(1, syslogMessages.size());
395+
396+
final SyslogMessage syslogMessage = syslogMessages.get(0);
397+
Assertions
398+
.assertEquals(
399+
"{\n" + " \"ClientIp\": \"127.0.0.1\",\n"
400+
+ " \"CorrelationId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
401+
+ " \"CrmOrganizationUniqueName\": \"Organization-1\",\n"
402+
+ " \"EntityId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
403+
+ " \"EntityName\": \"Entity-1\",\n" + " \"Fields\": \"{}\",\n"
404+
+ " \"InstanceUrl\": \"https://{uri1}\",\n" + " \"ItemType\": \"Message\",\n"
405+
+ " \"ItemUrl\": \"https://{uri1}/{uri2}/{uri3}\",\n"
406+
+ " \"Message\": \"Message 1\",\n" + " \"Operation\": \"Operation 1\",\n"
407+
+ " \"OrganizationId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
408+
+ " \"OriginalObjectId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
409+
+ " \"Query\": \"Query\",\n" + " \"QueryResults\": \"2\",\n"
410+
+ " \"ResultStatus\": \"Success\",\n"
411+
+ " \"ServiceContextId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
412+
+ " \"ServiceContextIdType\": \"Token 1\",\n" + " \"ServiceName\": \"Service 1\",\n"
413+
+ " \"SourceRecordId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
414+
+ " \"SourceSystem\": \"Azure\",\n"
415+
+ " \"SystemUserId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
416+
+ " \"TenantId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
417+
+ " \"TimeGenerated\": \"2025-10-06T00:00:00.0000000Z\",\n"
418+
+ " \"Type\": \"DataverseActivity\",\n"
419+
+ " \"UserAgent\": \"Mozilla/5.0 (<system-information>) <platform> (<platform-details>) <extensions>\",\n"
420+
+ " \"UserId\": \"[email protected]\",\n" + " \"UserKey\": \"UserKey-1\",\n"
421+
+ " \"UserType\": \"Admin\",\n" + " \"UserUpn\": \"[email protected]\",\n"
422+
+ " \"Workload\": \"Service1\",\n"
423+
+ " \"_ItemId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
424+
+ " \"_ResourceId\": \"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}\",\n"
425+
+ " \"_SubscriptionId\": \"bb41a487-309b-4d21-9ab8-2a8b948b2d18\",\n"
426+
+ " \"_TimeReceived\": \"2025-10-06T00:00:00.0000000Z\",\n"
427+
+ " \"_Internal_WorkspaceResourceId\": \"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName}\"\n"
428+
+ "}",
429+
syslogMessage.getMsg()
430+
);
431+
Assertions.assertEquals("md5-0ded52ef915af563e25778bf26b0f129-resourceName", syslogMessage.getHostname());
432+
Assertions.assertEquals("DataverseA_https://{uri1}/{uri2}/{uri3}", syslogMessage.getAppName());
433+
Assertions.assertEquals("2025-10-06T00:00:00Z", syslogMessage.getTimestamp());
434+
435+
final Map<String, Map<String, String>> sdElementMap = syslogMessage
436+
.getSDElements()
437+
.stream()
438+
.collect(Collectors.toMap((SDElement::getSdID), (sdElem) -> sdElem.getSdParams().stream().collect(Collectors.toMap(SDParam::getParamName, SDParam::getParamValue))));
439+
440+
Assertions.assertEquals(1, sdElementMap.get("nlf_01@48577").size());
441+
Assertions
442+
.assertEquals(DataverseActivityType.class.getSimpleName(), sdElementMap.get("nlf_01@48577").get("eventType"));
443+
444+
Assertions.assertTrue(sdElementMap.get("aer_02_event@48577").containsKey("properties"));
445+
}
446+
383447
@Test
384448
void adfActivityRunType() {
385449
final String json = Assertions

0 commit comments

Comments
 (0)