Skip to content

Commit 2b944cf

Browse files
authored
Merge pull request #4328 from hove-io/task_NAV_3691_otlp_grafana
📈 Integration of Opentelemetry (without instrument)
2 parents 999f629 + e64b0fd commit 2b944cf

21 files changed

+352
-14
lines changed

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ repos:
3333
args: [--ignore-missing-imports, --py2, --follow-imports, skip]
3434
files: source
3535
language_version: python3.9
36-
exclude: /monitor/|/third_party/|/tests/|/sql/|env\.py$|setup\.py$
36+
exclude: /monitor/|/third_party/|/tests/|/sql/|env\.py$|setup\.py|otlp\.py$

source/jormungandr/jormungandr/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
if app.config.get(str('PATCH_WITH_GEVENT_SOCKET'), False):
4848
init.patch_http(patch_level=app.config.get(str('PATCH_WITH_GEVENT_SOCKET_LEVEL'), "socket"))
4949

50+
from jormungandr import otlp
51+
5052
from jormungandr import new_relic
5153

5254
new_relic.init(app.config.get(str('NEWRELIC_CONFIG_PATH'), None))

source/jormungandr/jormungandr/api.py

+46-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from __future__ import absolute_import, print_function, unicode_literals, division
3434
import importlib
3535
from flask_restful.representations import json
36-
from flask import request, make_response, abort
36+
from flask import request, make_response, abort, g
3737
from jormungandr import rest_api, app, i_manager
3838
from jormungandr.index import index
3939
from jormungandr.modules_loader import ModulesLoader
@@ -44,6 +44,8 @@
4444
from jormungandr.authentication import get_user, get_token, get_app_name, get_used_coverages
4545
from jormungandr._version import __version__
4646
import six
47+
from jormungandr.otlp import otlp_instance
48+
import time
4749

4850

4951
@rest_api.representation("text/jsonp")
@@ -126,6 +128,49 @@ def add_info_newrelic(response, *args, **kwargs):
126128
return response
127129

128130

131+
def __get_otlp_coverages_label():
132+
used_coverages = get_used_coverages()
133+
134+
return ", ".join(sorted(used_coverages)) if used_coverages else "unknown"
135+
136+
137+
@app.after_request
138+
def record_request_call_to_otlp(response, *args, **kwargs):
139+
try:
140+
duration = time.time() - g.start
141+
token = get_token() if get_token() else "unknown"
142+
user = get_user(token=token, abort_if_no_token=False) if token != "unknown" else None
143+
user_id = str(user.id) if user else "unknown"
144+
token_name = get_app_name(token)
145+
token_name = token_name if token_name else "unknown"
146+
labels = {
147+
"token": token,
148+
"user_id": user_id,
149+
"token_name": token_name,
150+
"status": response.status_code,
151+
"coverages": __get_otlp_coverages_label(),
152+
}
153+
otlp_instance.send_request_call_metrics(duration, labels)
154+
except:
155+
logger = logging.getLogger(__name__)
156+
logger.exception('error while reporting to otlp from app.after_request')
157+
158+
return response
159+
160+
161+
@app.before_request
162+
def set_request_id():
163+
try:
164+
g.start = time.time()
165+
166+
otlp_instance.record_label("api", request.endpoint)
167+
otlp_instance.record_label("version", __version__)
168+
otlp_instance.record_label("coverage", __get_otlp_coverages_label())
169+
except:
170+
logger = logging.getLogger(__name__)
171+
logger.exception('error while reporting to otlp from app.before_request')
172+
173+
129174
# If modules are configured, then load and run them
130175
if 'MODULES' in rest_api.app.config:
131176
rest_api.module_loader = ModulesLoader(rest_api)

source/jormungandr/jormungandr/autocomplete/abstract_autocomplete.py

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from jormungandr.exceptions import UnknownObject, TechnicalError, log_exception
3535
import six
3636
from jormungandr.new_relic import record_custom_event
37+
from jormungandr.otlp import otlp_instance
3738

3839

3940
class AutocompleteError(RuntimeError):
@@ -79,6 +80,7 @@ def record_status(self, status, exc=None):
7980
if exc is not None:
8081
data["cause"] = str(exc)
8182
record_custom_event('autocomplete_status', data)
83+
otlp_instance.send_event_metrics('autocomplete_status', data)
8284

8385
def get_object_by_uri(self, uri, request_id=None, instances=None, current_datetime=None):
8486
"""

source/jormungandr/jormungandr/equipments/sytral.py

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from __future__ import absolute_import, print_function, unicode_literals, division
3131

3232
from jormungandr import cache, app, new_relic
33+
from jormungandr.otlp import otlp_instance
3334
from navitiacommon import type_pb2
3435
from dateutil import parser
3536
from jormungandr.utils import date_to_timestamp, PY3
@@ -118,6 +119,7 @@ def record_call(self, status, **kwargs):
118119
params = {'parking_system_id': "SytralRT", 'dataset': "sytral", 'status': status}
119120
params.update(kwargs)
120121
new_relic.record_custom_event('parking_status', params)
122+
otlp_instance.send_event_metrics('parking_status', params)
121123

122124
def _fill_equipment_details(self, equipment_form_web_service, equipment_details):
123125
equipment_details.id = equipment_form_web_service['id']

source/jormungandr/jormungandr/exceptions.py

+19
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
from werkzeug.exceptions import HTTPException
3333
import logging
3434
from jormungandr.new_relic import record_exception
35+
from jormungandr.otlp import otlp_instance
36+
from typing import Dict
3537

3638
__all__ = [
3739
"RegionNotFound",
@@ -49,6 +51,10 @@ def format_error(code, message):
4951
return error
5052

5153

54+
def format_otlp_error(data):
55+
return {"error_id": data["error"]["id"], "error_message": data["error"]["message"]}
56+
57+
5258
class RegionNotFound(HTTPException):
5359
def __init__(self, region=None, lon=None, lat=None, object_id=None, custom_msg=None):
5460
super(RegionNotFound, self).__init__()
@@ -76,6 +82,7 @@ def __init__(self, region=None, lon=None, lat=None, object_id=None, custom_msg=N
7682
self.data = format_error("unknown_object", "Invalid id : {id}".format(id=object_id))
7783
else:
7884
self.data = format_error("unknown_object", "Unable to parse region")
85+
otlp_instance.record_exception(self, format_otlp_error(self.data))
7986

8087
def __str__(self):
8188
return repr(self.data['message'])
@@ -87,6 +94,7 @@ def __init__(self, region, path):
8794
error = 'The region {} is dead'.format(region)
8895
self.data = format_error("dead_socket", error)
8996
self.code = 503
97+
otlp_instance.record_exception(self, format_otlp_error(self.data))
9098

9199

92100
class ApiNotFound(HTTPException):
@@ -95,6 +103,7 @@ def __init__(self, api):
95103
error = 'The api {} doesn\'t exist'.format(api)
96104
self.data = format_error("unknown_object", error)
97105
self.code = 404
106+
otlp_instance.record_exception(self, format_otlp_error(self.data))
98107

99108

100109
class UnknownObject(HTTPException):
@@ -103,27 +112,31 @@ def __init__(self, msg):
103112
error = 'The object {} doesn\'t exist'.format(msg)
104113
self.data = format_error("unknown_object", error)
105114
self.code = 404
115+
otlp_instance.record_exception(self, format_otlp_error(self.data))
106116

107117

108118
class InvalidArguments(HTTPException):
109119
def __init__(self, arg):
110120
super(InvalidArguments, self).__init__()
111121
self.data = format_error("unknown_object", "Invalid arguments " + arg)
112122
self.code = 400
123+
otlp_instance.record_exception(self, format_otlp_error(self.data))
113124

114125

115126
class UnableToParse(HTTPException):
116127
def __init__(self, msg):
117128
super(UnableToParse, self).__init__()
118129
self.data = format_error("unable_to_parse", msg)
119130
self.code = 400
131+
otlp_instance.record_exception(self, format_otlp_error(self.data))
120132

121133

122134
class TechnicalError(HTTPException):
123135
def __init__(self, msg):
124136
super(TechnicalError, self).__init__()
125137
self.data = format_error("technical_error", msg)
126138
self.code = 500
139+
otlp_instance.record_exception(self, format_otlp_error(self.data))
127140

128141

129142
# Only used by geovelo streetnetwork
@@ -132,27 +145,31 @@ def __init__(self, msg):
132145
super(GeoveloTechnicalError, self).__init__()
133146
self.data = format_error("technical_error", msg)
134147
self.code = 500
148+
otlp_instance.record_exception(self, format_otlp_error(self.data))
135149

136150

137151
class HandimapTechnicalError(HTTPException):
138152
def __init__(self, msg):
139153
super(HandimapTechnicalError, self).__init__()
140154
self.data = format_error("technical_error", msg)
141155
self.code = 500
156+
otlp_instance.record_exception(self, format_otlp_error(self.data))
142157

143158

144159
class AndyamoTechnicalError(HTTPException):
145160
def __init__(self, msg):
146161
super(AndyamoTechnicalError, self).__init__()
147162
self.data = format_error("technical_error", msg)
148163
self.code = 500
164+
otlp_instance.record_exception(self, format_otlp_error(self.data))
149165

150166

151167
class ConfigException(Exception):
152168
def __init__(self, arg):
153169
super(ConfigException, self).__init__(arg)
154170
self.data = format_error("config_exception", "Invalid config " + arg)
155171
self.code = 400
172+
otlp_instance.record_exception(self, format_otlp_error(self.data))
156173

157174

158175
def log_exception(sender, exception, **extra):
@@ -166,9 +183,11 @@ def log_exception(sender, exception, **extra):
166183
logger.debug(error)
167184
if exception.code >= 500:
168185
record_exception()
186+
otlp_instance.record_exception(exception)
169187
else:
170188
logger.exception(error)
171189
record_exception()
190+
otlp_instance.record_exception(exception)
172191

173192

174193
class StatManagerError(RuntimeError):

source/jormungandr/jormungandr/external_services/external_service.py

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import logging
3737
import requests as requests
3838
from six.moves.urllib.parse import urlencode
39+
from jormungandr.otlp import otlp_instance
3940

4041

4142
class ExternalServiceError(RuntimeError):
@@ -79,6 +80,7 @@ def record_call(self, url, status, **kwargs):
7980
params = {'external_service_id': "Forseti", 'status': status, 'external_service_url': url}
8081
params.update(kwargs)
8182
new_relic.record_custom_event('external_service_status', params)
83+
otlp_instance.send_event_metrics('external_service_status', params)
8284

8385
@abc.abstractmethod
8486
def get_response(self, arguments):

source/jormungandr/jormungandr/interfaces/v1/Journeys.py

-1
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,6 @@ def wrapper(*args, **kwargs):
484484
class Journeys(JourneyCommon):
485485
def __init__(self):
486486
# journeys must have a custom authentication process
487-
488487
super(Journeys, self).__init__(output_type_serializer=api.JourneysSerializer)
489488

490489
parser_get = self.parsers["get"]

source/jormungandr/jormungandr/new_relic.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from typing import Text, Callable
3737
from contextlib import contextmanager
3838
from jormungandr import app
39+
from jormungandr.otlp import otlp_instance
3940

4041
try:
4142
from newrelic import agent
@@ -130,9 +131,10 @@ def get_common_event_params(service_name, call_name, status="ok"):
130131
}
131132

132133

134+
# TODO: Update and move this function into otlp.py when we will remove newrelic
133135
def distributedEvent(call_name, group_name):
134136
"""
135-
Custom event that we publish to New Relic for distributed scenario
137+
Custom event that we publish to New Relic and Grafana for distributed scenario
136138
"""
137139

138140
def wrap(func):
@@ -148,13 +150,16 @@ def wrapper(obj, service, *args, **kwargs):
148150
except Exception as e:
149151
event_params["status"] = "failed"
150152
event_params.update({"exception": e})
153+
otlp_instance.record_exception(e, event_params)
151154
raise
152155

153156
duration = timeit.default_timer() - start_time
154157
event_params.update({"duration": duration})
155158

156159
# Send the custom event to newrelic !
157160
record_custom_event("distributed", event_params)
161+
# Send metrics to otlp
162+
otlp_instance.send_event_metrics("distributed", event_params)
158163

159164
return result
160165

@@ -163,6 +168,7 @@ def wrapper(obj, service, *args, **kwargs):
163168
return wrap
164169

165170

171+
# TODO: Update and move this function into otlp.py when we will remove newrelic
166172
@contextmanager
167173
def record_streetnetwork_call(call_name, connector_name, mode, coverage_name):
168174
"""
@@ -179,15 +185,19 @@ def record_streetnetwork_call(call_name, connector_name, mode, coverage_name):
179185
except Exception as e:
180186
event_params["status"] = "failed"
181187
event_params.update({"exception": e})
188+
otlp_instance.record_exception(e, event_params)
182189
raise
183190

184191
duration = timeit.default_timer() - start_time
185192
event_params.update({"duration": duration})
186193

187194
# Send the custom event to newrelic !
188195
record_custom_event(newrelic_service_name, event_params)
196+
# Send metrics to otlp
197+
otlp_instance.send_event_metrics(newrelic_service_name, event_params)
189198

190199

200+
# TODO: Update and move this function into otlp.py when we will remove newrelic
191201
def statManagerEvent(call_name, group_name):
192202
"""
193203
Custom event that we publish to New Relic for stat_manager
@@ -205,13 +215,16 @@ def wrapper(obj, service, *args, **kwargs):
205215
except Exception as e:
206216
event_params["status"] = "failed"
207217
event_params.update({"reason": str(e)})
218+
otlp_instance.record_exception(e, event_params)
208219
raise
209220
finally:
210221
duration = timeit.default_timer() - start_time
211222
event_params.update({"duration": duration})
212223

213224
# Send the custom event to newrelic !
214225
record_custom_event("stat_manager", event_params)
226+
# Send metrics to otlp
227+
otlp_instance.send_event_metrics("stat_manager", event_params)
215228

216229
return wrapper
217230

0 commit comments

Comments
 (0)