Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add observability via OpenTelemetry #257

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 106 additions & 100 deletions memberportal/api_spacedirectory/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .models import SpaceAPI, SpaceAPISensor, SpaceAPISensorProperties
from profile.models import Profile
from api_general.models import SiteSession
from otel import tracer
import json


Expand All @@ -15,108 +16,113 @@ class SpaceDirectoryStatus(APIView):
permission_classes = (permissions.AllowAny,)

def get(self, request):
if not config.ENABLE_SPACE_DIRECTORY:
return Response(
"Space Directory is not enabled on this server.",
status=status.HTTP_404_NOT_FOUND,
)

else:
spaceapi = {
"space": config.SITE_OWNER,
"logo": config.SITE_LOGO,
"url": config.MAIN_SITE_URL,
"contact": {
"email": config.SPACE_DIRECTORY_CONTACT_EMAIL,
"twitter": config.SPACE_DIRECTORY_CONTACT_TWITTER,
"phone": config.SPACE_DIRECTORY_CONTACT_PHONE,
"facebook": config.SPACE_DIRECTORY_CONTACT_FACEBOOK,
},
"spacefed": {
"spacenet": config.SPACE_DIRECTORY_FED_SPACENET,
"spacesaml": config.SPACE_DIRECTORY_FED_SPACESAML,
"spacephone": config.SPACE_DIRECTORY_FED_SPACEPHONE,
},
"projects": json.loads(config.SPACE_DIRECTORY_PROJECTS),
"issue_report_channels": ["email"],
}

# Get the default data
spaceapi_data = SpaceAPI.objects.get()

# Get a list of all the sensors
spaceapi_sensors = SpaceAPISensor.objects.all()

# Create an empty dict to add the sensor data to
sensor_data = {}

# Iterate over the sensors and update the dict as appropriate
for sensor in spaceapi_sensors:
sensor_details = {}
# Do we already have a sensor of this type? If not, create it now
if sensor.sensor_type not in sensor_data:
sensor_data[sensor.sensor_type] = []

## Setup the basic details
sensor_details = {
"name": sensor.name,
"description": sensor.description or "",
"location": sensor.location,
with tracer.start_as_current_span("get_spaceapi_details"):
if not config.ENABLE_SPACE_DIRECTORY:
return Response(
"Space Directory is not enabled on this server.",
status=status.HTTP_404_NOT_FOUND,
)

else:
spaceapi = {
"space": config.SITE_OWNER,
"logo": config.SITE_LOGO,
"url": config.MAIN_SITE_URL,
"contact": {
"email": config.SPACE_DIRECTORY_CONTACT_EMAIL,
"twitter": config.SPACE_DIRECTORY_CONTACT_TWITTER,
"phone": config.SPACE_DIRECTORY_CONTACT_PHONE,
"facebook": config.SPACE_DIRECTORY_CONTACT_FACEBOOK,
},
"spacefed": {
"spacenet": config.SPACE_DIRECTORY_FED_SPACENET,
"spacesaml": config.SPACE_DIRECTORY_FED_SPACESAML,
"spacephone": config.SPACE_DIRECTORY_FED_SPACEPHONE,
},
"projects": json.loads(config.SPACE_DIRECTORY_PROJECTS),
"issue_report_channels": ["email"],
}

### Do we have properties? If so, let's add them
if len(sensor.properties.all()) > 0:
sensor_details["properties"] = {}
for prop in sensor.properties.all():
properties = {
prop.name: {"value": prop.value, "unit": prop.unit}
}
sensor_details["properties"].update(properties)
else:
sensor_details.update({"value": sensor.value, "unit": sensor.unit})

sensor_data[sensor.sensor_type].append(sensor_details)

## Add the user count and members on site count to the sensors
spaceapi_user_count = Profile.objects.all().filter(state="active").count()
spaceapi_members_on_site = SiteSession.objects.filter(
signout_date=None
).order_by("-signin_date")

sensor_data["total_member_count"] = [{"value": spaceapi_user_count}]

sensor_data["people_now_present"] = [
{"value": spaceapi_members_on_site.count()}
]

# Is the camera array empty? If not, add them
if not config.SPACE_DIRECTORY_CAMS:
spaceapi["cameras"] = config.SPACE_DIRECTORY_CAMS

# Set the STATE part of the schema, the icons, and the schema version
spaceapi["state"] = {
"open": spaceapi_data.space_is_open,
"message": spaceapi_data.space_message,
"lastchange": spaceapi_data.status_last_change.timestamp(),
}
spaceapi["icon"] = {
"open": config.SPACE_DIRECTORY_ICON_OPEN,
"closed": config.SPACE_DIRECTORY_ICON_CLOSED,
}
spaceapi["api_compatibility"] = ["14"]

## Add the sensor data to the main body of the schema
spaceapi["sensors"] = sensor_data

## Add the location data based on the values in Constance
spaceapi["location"] = {
"address": config.SPACE_DIRECTORY_LOCATION_ADDRESS,
"lat": config.SPACE_DIRECTORY_LOCATION_LAT,
"lon": config.SPACE_DIRECTORY_LOCATION_LON,
}

# Return the JSON document
return Response(spaceapi)
# Get the default data
spaceapi_data = SpaceAPI.objects.get()

# Get a list of all the sensors
spaceapi_sensors = SpaceAPISensor.objects.all()

# Create an empty dict to add the sensor data to
sensor_data = {}

# Iterate over the sensors and update the dict as appropriate
for sensor in spaceapi_sensors:
sensor_details = {}
# Do we already have a sensor of this type? If not, create it now
if sensor.sensor_type not in sensor_data:
sensor_data[sensor.sensor_type] = []

## Setup the basic details
sensor_details = {
"name": sensor.name,
"description": sensor.description or "",
"location": sensor.location,
}

### Do we have properties? If so, let's add them
if len(sensor.properties.all()) > 0:
sensor_details["properties"] = {}
for prop in sensor.properties.all():
properties = {
prop.name: {"value": prop.value, "unit": prop.unit}
}
sensor_details["properties"].update(properties)
else:
sensor_details.update(
{"value": sensor.value, "unit": sensor.unit}
)

sensor_data[sensor.sensor_type].append(sensor_details)

## Add the user count and members on site count to the sensors
spaceapi_user_count = (
Profile.objects.all().filter(state="active").count()
)
spaceapi_members_on_site = SiteSession.objects.filter(
signout_date=None
).order_by("-signin_date")

sensor_data["total_member_count"] = [{"value": spaceapi_user_count}]

sensor_data["people_now_present"] = [
{"value": spaceapi_members_on_site.count()}
]

# Is the camera array empty? If not, add them
if not config.SPACE_DIRECTORY_CAMS:
spaceapi["cameras"] = config.SPACE_DIRECTORY_CAMS

# Set the STATE part of the schema, the icons, and the schema version
spaceapi["state"] = {
"open": spaceapi_data.space_is_open,
"message": spaceapi_data.space_message,
"lastchange": spaceapi_data.status_last_change.timestamp(),
}
spaceapi["icon"] = {
"open": config.SPACE_DIRECTORY_ICON_OPEN,
"closed": config.SPACE_DIRECTORY_ICON_CLOSED,
}
spaceapi["api_compatibility"] = ["14"]

## Add the sensor data to the main body of the schema
spaceapi["sensors"] = sensor_data

## Add the location data based on the values in Constance
spaceapi["location"] = {
"address": config.SPACE_DIRECTORY_LOCATION_ADDRESS,
"lat": config.SPACE_DIRECTORY_LOCATION_LAT,
"lon": config.SPACE_DIRECTORY_LOCATION_LON,
}

# Return the JSON document
return Response(spaceapi)


class SpaceDirectoryUpdate(APIView):
Expand Down
7 changes: 7 additions & 0 deletions memberportal/membermatters/asgi.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import os

from otel import trace, OpenTelemetryMiddleware

# INTERNAL URLS
from django.conf.urls import url
from django.core.asgi import get_asgi_application


os.environ.setdefault("DJANGO_SETTINGS_MODULE", "membermatters.settings")
django_asgi_app = get_asgi_application()

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from membermatters.websocket_urls import urlpatterns


application = ProtocolTypeRouter(
{
"http": django_asgi_app,
"websocket": AuthMiddlewareStack(URLRouter(urlpatterns)),
}
)
application = OpenTelemetryMiddleware(application)
4 changes: 2 additions & 2 deletions memberportal/membermatters/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,9 +264,9 @@
# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

LANGUAGE_CODE = "en-au"
LANGUAGE_CODE = os.environ.get("MM_LOCALE", "en-au")

TIME_ZONE = "Australia/Brisbane"
TIME_ZONE = os.environ.get("MM_TZ", "Australia/Brisbane")
USE_I18N = True
USE_L10N = True
USE_TZ = True
Expand Down
40 changes: 40 additions & 0 deletions memberportal/otel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import os

## Tracing
from opentelemetry import trace, context, propagate
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware

## Logging
from opentelemetry._logs import set_logger_provider
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor

### OTEL EXPORTER SETUP ###

resource = Resource(
attributes={
"service.name": os.environ.get("MM_OTEL_SVC_NAME", "member_portal_backend"),
"deployment.environment": os.environ.get("MM_ENV", "development"),
"service.namespace": "membermatters",
}
)

#### TRACES
trace.set_tracer_provider(TracerProvider(resource=resource))
tracer = trace.get_tracer(__name__)

otlp_trace_exporter = OTLPSpanExporter(endpoint="http://localhost:4318/v1/traces")

span_processor = BatchSpanProcessor(otlp_trace_exporter)

trace.get_tracer_provider().add_span_processor(span_processor)

#### LOGS
logger_provider = LoggerProvider(resource=resource)
otlp_logs_exporter = OTLPLogExporter(endpoint="http://localhost:4318/v1/logs")
logger_provider.add_log_record_processor(BatchLogRecordProcessor(otlp_logs_exporter))
Loading
Loading