Skip to content

Commit

Permalink
Merge pull request #4456 from telefonicaid/feature/4449_notification_…
Browse files Browse the repository at this point in the history
…payload_in_logs

ADD notification payload in INFO log traces
  • Loading branch information
AlvaroVega authored Dec 21, 2023
2 parents c2ceb8d + 031ddf6 commit 1d12e25
Show file tree
Hide file tree
Showing 11 changed files with 343 additions and 71 deletions.
1 change: 1 addition & 0 deletions CHANGES_NEXT_RELEASE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
- Add: servicePath field to builtin attributes (#2877)
- Add: notification.mqtt.retain and notification.mqttCustom.retain flag for MQTT retain in notifications (#4388)
- Add: notification payload in INFO log traces (#4449)
- Fix: correctly detect JSON attribute and metadata value changes in subscription triggering logic (#4211, #4434, #643)
- Fix: DateTime and geo:json types were not supported in custom notifications using ngsi patching (#4435)
- Fix: logDeprecate not working correctly (`geo:json` wrongly considered as deprecated)
Expand Down
22 changes: 11 additions & 11 deletions doc/manuals/admin/logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,13 @@ time=2020-10-26T10:32:41.724Z | lvl=INFO | corr=93bdc5b4-1776-11eb-954d-000c29df
different value for the correlator.

```
time=2020-10-26T10:32:22.145Z | lvl=INFO | corr=87f708a8-1776-11eb-b327-000c29df7908; cbnotif=1 | trans=1603707992-318-00000000003 | from=0.0.0.0 | srv=s1| subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f914177334436ea590f6edb): POST localhost:1028/accumulate, response code: 200
time=2020-10-26T10:32:22.145Z | lvl=INFO | corr=87f708a8-1776-11eb-b327-000c29df7908; cbnotif=1 | trans=1603707992-318-00000000003 | from=0.0.0.0 | srv=s1| subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f914177334436ea590f6edb): POST localhost:1028/accumulate, payload (123 bytes): {"subscriptionId":"5f914177334436ea590f6edb","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: 200
```

* In the case of MQTT notifications the `msg` field of the above trace is slightly different:

```
time=2020-10-26T10:32:22.145Z | lvl=INFO | corr=87f708a8-1776-11eb-b327-000c29df7908; cbnotif=1 | trans=1603707992-318-00000000003 | from=0.0.0.0 | srv=s1| subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoMqttNotification | msg=MQTT Notif delivered (subId: 60ffea6c1bca454f9a64c96c): broker: localhost:1883, topic: sub2
time=2020-10-26T10:32:22.145Z | lvl=INFO | corr=87f708a8-1776-11eb-b327-000c29df7908; cbnotif=1 | trans=1603707992-318-00000000003 | from=0.0.0.0 | srv=s1| subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoMqttNotification | msg=MQTT Notif delivered (subId: 60ffea6c1bca454f9a64c96c): broker: localhost:1883, topic: sub2, payload (123 bytes): {"subscriptionId":"60ffea6c1bca454f9a64c96c","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}
```

* For each forwarded request to a [Context Provider](../user/context_providers.md) (either queries or updates),
Expand All @@ -200,7 +200,7 @@ Some additional considerations:
occurs. For instance:

```
time=2020-10-26T10:32:22.145Z | lvl=INFO | corr=87f708a8-1776-11eb-b327-000c29df7908; cbnotif=1 | trans=1603707992-318-00000000003 | from=0.0.0.0 | srv=s1| subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f914177334436ea590f6edb): POST localhost:1028/accumulate, response code: Couldn't connect to server
time=2020-10-26T10:32:22.145Z | lvl=INFO | corr=87f708a8-1776-11eb-b327-000c29df7908; cbnotif=1 | trans=1603707992-318-00000000003 | from=0.0.0.0 | srv=s1| subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f914177334436ea590f6edb): POST localhost:1028/accumulate, payload (123 bytes): {"subscriptionId":"5f914177334436ea590f6edb","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: Couldn't connect to server
```

* When a client request triggers forwarding to Context Providers, a `Starting forwarding for <client request URL>`
Expand Down Expand Up @@ -346,56 +346,56 @@ contextBroker -fg -httpTimeout 10000 -logLevel INFO -notificationMode threadpool
Successful sent (response code 200):

```
time=2020-10-26T14:48:37.192Z | lvl=INFO | corr=54393a44-179a-11eb-bb87-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000006 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e174b14e7532482ac794): POST localhost:1028/accumulate, response code: 200
time=2020-10-26T14:48:37.192Z | lvl=INFO | corr=54393a44-179a-11eb-bb87-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000006 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e174b14e7532482ac794): POST localhost:1028/accumulate, payload (123 bytes): {"subscriptionId":"5f96e174b14e7532482ac794","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: 200
```

Notification endpoint response with 400 (a WARN trace is printed):

```
time=2020-10-26T14:49:34.619Z | lvl=WARN | corr=7689f6ba-179a-11eb-ac4c-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000009 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=httpRequestSend.cpp[583]:httpRequestSend | msg=Notification (subId: 5f96e1fdb14e7532482ac795) response NOT OK, http code: 400
time=2020-10-26T14:49:34.619Z | lvl=INFO | corr=7689f6ba-179a-11eb-ac4c-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000009 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e1fdb14e7532482ac795): POST localhost:1028/giveme400, response code: 400
time=2020-10-26T14:49:34.619Z | lvl=INFO | corr=7689f6ba-179a-11eb-ac4c-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000009 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e1fdb14e7532482ac795): POST localhost:1028/giveme400, payload (123 bytes): {"subscriptionId":"5f96e1fdb14e7532482ac795","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: 400
```

Notification endpoint response with 404 (a WARN trace is printed):

```
time=2020-10-26T14:51:40.764Z | lvl=WARN | corr=c1b8e9c0-179a-11eb-9edc-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000012 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=httpRequestSend.cpp[583]:httpRequestSend | msg=Notification (subId: 5f96e27cb14e7532482ac796) response NOT OK, http code: 404
time=2020-10-26T14:51:40.764Z | lvl=INFO | corr=c1b8e9c0-179a-11eb-9edc-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000012 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e27cb14e7532482ac796): POST localhost:1028/giveme404, response code: 404
time=2020-10-26T14:51:40.764Z | lvl=INFO | corr=c1b8e9c0-179a-11eb-9edc-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000012 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e27cb14e7532482ac796): POST localhost:1028/giveme404, payload (123 bytes): {"subscriptionId":"5f96e27cb14e7532482ac796","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: 404
```

Notification endpoint response with 500 (a WARN trace is printed)

```
time=2020-10-26T14:53:04.246Z | lvl=WARN | corr=f37b5024-179a-11eb-9ce6-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000015 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=httpRequestSend.cpp[583]:httpRequestSend | msg=Notification (subId: 5f96e2cfb14e7532482ac797) response NOT OK, http code: 500
time=2020-10-26T14:53:04.247Z | lvl=INFO | corr=f37b5024-179a-11eb-9ce6-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000015 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e2cfb14e7532482ac797): POST localhost:1028/giveme500, response code: 500
time=2020-10-26T14:53:04.247Z | lvl=INFO | corr=f37b5024-179a-11eb-9ce6-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000015 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e2cfb14e7532482ac797): POST localhost:1028/giveme500, payload (123 bytes): {"subscriptionId":"5f96e2cfb14e7532482ac797","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: 500
```

Endpoint not responding within 10 seconds timeout or some other connection error (alarm is raised in WARN level):

```
time=2020-10-26T14:54:15.996Z | lvl=WARN | corr=184b8b80-179b-11eb-9c52-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000018 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=AlarmManager.cpp[328]:notificationError | msg=Raising alarm NotificationError localhost:1028/givemeDelay: notification failure for queue worker: Timeout was reached
time=2020-10-26T14:54:15.996Z | lvl=INFO | corr=184b8b80-179b-11eb-9c52-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000018 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e30db14e7532482ac798): POST localhost:1028/givemeDelay, response code: Timeout was reached
time=2020-10-26T14:54:15.996Z | lvl=INFO | corr=184b8b80-179b-11eb-9c52-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000018 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e30db14e7532482ac798): POST localhost:1028/givemeDelay, payload (123 bytes): {"subscriptionId":"5f96e30db14e7532482ac798","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: Timeout was reached
```

Endpoint in not responding port, e.g. localhost:9999 (alarm is raised in WARN log level):

```
time=2020-10-26T15:01:50.659Z | lvl=WARN | corr=2d3e4cfc-179c-11eb-b667-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000030 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=AlarmManager.cpp[328]:notificationError | msg=Raising alarm NotificationError localhost:9999/giveme: notification failure for queue worker: Couldn't connect to server
time=2020-10-26T15:01:50.659Z | lvl=INFO | corr=2d3e4cfc-179c-11eb-b667-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000030 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e4deb14e7532482ac79c): POST localhost:9999/giveme, response code: Couldn't connect to server
time=2020-10-26T15:01:50.659Z | lvl=INFO | corr=2d3e4cfc-179c-11eb-b667-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000030 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e4deb14e7532482ac79c): POST localhost:9999/giveme, payload (123 bytes): {"subscriptionId":"5f96e4deb14e7532482ac79c","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: Couldn't connect to server
```

Endpoint in unresolvable name, e.g. foo.bar.bar.com (alarm is raised in WARN log level):

```
time=2020-10-26T15:03:54.258Z | lvl=WARN | corr=769f8d8e-179c-11eb-960f-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000033 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=AlarmManager.cpp[328]:notificationError | msg=Raising alarm NotificationError foo.bar.bar.com:9999/giveme: notification failure for queue worker: Couldn't resolve host name
time=2020-10-26T15:03:54.258Z | lvl=INFO | corr=769f8d8e-179c-11eb-960f-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000033 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e559b14e7532482ac79d): POST foo.bar.bar.com:9999/giveme, response code: Couldn't resolve host name
time=2020-10-26T15:03:54.258Z | lvl=INFO | corr=769f8d8e-179c-11eb-960f-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000033 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e559b14e7532482ac79d): POST foo.bar.bar.com:9999/giveme, payload (123 bytes): {"subscriptionId":"5f96e559b14e7532482ac79d","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: Couldn't resolve host name
```

Endpoint in unreachable IP, e.g. 12.34.56.87 (alarm is raised in WARN log level):

```
time=2020-10-26T15:06:14.642Z | lvl=WARN | corr=c4a3192e-179c-11eb-ac8f-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000036 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=AlarmManager.cpp[328]:notificationError | msg=Raising alarm NotificationError 12.34.56.78:9999/giveme: notification failure for queue worker: Timeout was reached
time=2020-10-26T15:06:14.642Z | lvl=INFO | corr=c4a3192e-179c-11eb-ac8f-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000036 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e5dbb14e7532482ac79e): POST 12.34.56.78:9999/giveme, response code: Timeout was reached
time=2020-10-26T15:06:14.642Z | lvl=INFO | corr=c4a3192e-179c-11eb-ac8f-000c29df7908; cbnotif=1 | trans=1603722272-416-00000000036 | from=0.0.0.0 | srv=s1 | subsrv=/A | comp=Orion | op=logTracing.cpp[63]:logInfoHttpNotification | msg=Notif delivered (subId: 5f96e5dbb14e7532482ac79e): POST 12.34.56.78:9999/giveme, payload (123 bytes): {"subscriptionId":"5f96e5dbb14e7532482ac79e","data":[{"id":"E","type":"T","A":{"type":"Number","value":42,"metadata":{}}}]}, response code: Timeout was reached
```

[Top](#top)
Expand Down
91 changes: 65 additions & 26 deletions src/lib/common/logTracing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,28 @@ inline bool isNgsiV1Url(const char* url)



/* ****************************************************************************
*
* truncatePayload -
*
* NOTE: this function allocated dynamic memory, be careful with memory leaks!
*/
static char* truncatePayload(const char* payload)
{
// +5 due to "(...)"
// +1 due to '\0'
unsigned int truncatedPayloadLengh = logInfoPayloadMaxSize + 5 + 1;

char* truncatedPayload = (char*) malloc(logInfoPayloadMaxSize + 5 + 1);
strncpy(truncatedPayload, payload, logInfoPayloadMaxSize);
strncpy(truncatedPayload + logInfoPayloadMaxSize, "(...)", 5);
truncatedPayload[truncatedPayloadLengh - 1] = '\0';

return truncatedPayload;
}



/* ****************************************************************************
*
* logInfoNotification - rc as int
Expand All @@ -52,12 +74,13 @@ void logInfoHttpNotification
const char* endpoint,
const char* verb,
const char* resource,
const char* payload,
int rc
)
{
char buffer[STRING_SIZE_FOR_INT];
snprintf(buffer, sizeof(buffer), "%d", rc);
logInfoHttpNotification(subId, endpoint, verb, resource, buffer);
logInfoHttpNotification(subId, endpoint, verb, resource, payload, buffer);
}


Expand All @@ -72,10 +95,29 @@ void logInfoHttpNotification
const char* endpoint,
const char* verb,
const char* resource,
const char* payload,
const char* rc
)
{
LM_I(("Notif delivered (subId: %s): %s %s%s, response code: %s", subId, verb, endpoint, resource, rc));
bool cleanAfterUse = false;
char* effectivePayload;

if (strlen(payload) > logInfoPayloadMaxSize)
{
effectivePayload = truncatePayload(payload);
cleanAfterUse = true;
}
else
{
effectivePayload = (char*) payload;
}

LM_I(("Notif delivered (subId: %s): %s %s%s, payload (%d bytes): %s, response code: %s", subId, verb, endpoint, resource, strlen(payload), effectivePayload, rc));

if (cleanAfterUse)
{
free(effectivePayload);
}
}


Expand All @@ -88,10 +130,29 @@ void logInfoMqttNotification
(
const char* subId,
const char* endpoint,
const char* resource
const char* resource,
const char* payload
)
{
LM_I(("MQTT Notif delivered (subId: %s): broker: %s, topic: %s", subId, endpoint, resource));
bool cleanAfterUse = false;
char* effectivePayload;

if (strlen(payload) > logInfoPayloadMaxSize)
{
effectivePayload = truncatePayload(payload);
cleanAfterUse = true;
}
else
{
effectivePayload = (char*) payload;
}

LM_I(("MQTT Notif delivered (subId: %s): broker: %s, topic: %s, payload (%d bytes): %s", subId, endpoint, resource, strlen(payload), effectivePayload));

if (cleanAfterUse)
{
free(effectivePayload);
}
}


Expand All @@ -117,28 +178,6 @@ void logInfoRequestWithoutPayload



/* ****************************************************************************
*
* truncatePayload -
*
* NOTE: this function allocated dynamic memory, be careful with memory leaks!
*/
static char* truncatePayload(const char* payload)
{
// +5 due to "(...)"
// +1 due to '\0'
unsigned int truncatedPayloadLengh = logInfoPayloadMaxSize + 5 + 1;

char* truncatedPayload = (char*) malloc(logInfoPayloadMaxSize + 5 + 1);
strncpy(truncatedPayload, payload, logInfoPayloadMaxSize);
strncpy(truncatedPayload + logInfoPayloadMaxSize, "(...)", 5);
truncatedPayload[truncatedPayloadLengh - 1] = '\0';

return truncatedPayload;
}



/* ****************************************************************************
*
* logInfoRequestWithPayload -
Expand Down
9 changes: 6 additions & 3 deletions src/lib/common/logTracing.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,31 @@

/* ****************************************************************************
*
* logInfoNotification - rc as int
* logInfoHttpNotification - rc as int
*/
extern void logInfoHttpNotification
(
const char* subId,
const char* endpoint,
const char* verb,
const char* resource,
const char* payload,
int rc
);



/* ****************************************************************************
*
* logInfoNotification - rc as string
* logInfoHttpNotification - rc as string
*/
extern void logInfoHttpNotification
(
const char* subId,
const char* endpoint,
const char* verb,
const char* resource,
const char* payload,
const char* rc
);

Expand All @@ -68,7 +70,8 @@ extern void logInfoMqttNotification
(
const char* subId,
const char* endpoint,
const char* resource
const char* resource,
const char* payload
);


Expand Down
6 changes: 3 additions & 3 deletions src/lib/ngsiNotify/doNotify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,11 @@ static void doNotifyHttp(SenderThreadParams* params, CURL* curl, SyncQOverflow<S
// Add notificacion result summary in log INFO level
if (statusCode != -1)
{
logInfoHttpNotification(params->subscriptionId.c_str(), endpoint.c_str(), params->verb.c_str(), params->resource.c_str(), statusCode);
logInfoHttpNotification(params->subscriptionId.c_str(), endpoint.c_str(), params->verb.c_str(), params->resource.c_str(), params->content.c_str(), statusCode);
}
else
{
logInfoHttpNotification(params->subscriptionId.c_str(), endpoint.c_str(), params->verb.c_str(), params->resource.c_str(), out.c_str());
logInfoHttpNotification(params->subscriptionId.c_str(), endpoint.c_str(), params->verb.c_str(), params->resource.c_str(), params->content.c_str(), out.c_str());
}
}

Expand Down Expand Up @@ -164,7 +164,7 @@ static void doNotifyMqtt(SenderThreadParams* params)
// mqttOnPublishCallback is called (by the moment we are not doing nothing there, just printing in
// DEBUG log level). Note however that even if mqttOnPublishCallback() is called there is no actual
// guarantee if MQTT QoS is 0
logInfoMqttNotification(params->subscriptionId.c_str(), endpoint.c_str(), params->resource.c_str());
logInfoMqttNotification(params->subscriptionId.c_str(), endpoint.c_str(), params->resource.c_str(), params->content.c_str());
subNotificationErrorStatus(params->tenant, params->subscriptionId, false, -1, "");
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ brokerStart CB 0 IPV4 -logDeprecate

#
# 01. Query E1-T1
# 02. GET /v1/contextEntities/E
# 02. GET /v1/contextEntities/E/attributes/A
# 03. Create entity using NGSIv1 metadata location
# 04. Create entity using NGSIv1 and geo:point
# 05. Create entity using NGSIv2 and geo:point
Expand Down
Loading

0 comments on commit 1d12e25

Please sign in to comment.