Skip to content
This repository was archived by the owner on May 23, 2023. It is now read-only.

Commit 236db4b

Browse files
Merge pull request #6 from madeiramadeirabr/health_check
Implementação de health check de monitoramento
2 parents 5e520a5 + f92291c commit 236db4b

File tree

17 files changed

+624
-12
lines changed

17 files changed

+624
-12
lines changed

examples/lambda_api/app.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
env = helper.get_environment()
99
load_dot_env(env)
1010

11-
# load env
11+
from lambda_app.services.v1.healthcheck import HealthCheckSchema
12+
from lambda_app.services.v1.healthcheck.resources import MysqlConnectionHealthCheck, RedisConnectionHealthCheck, \
13+
SQSConnectionHealthCheck, SelfConnectionHealthCheck
14+
from lambda_app.services.v1.healthcheck_service import HealthCheckService
1215
from lambda_app.config import get_config
1316
from lambda_app.enums.events import EventType
1417
from lambda_app.enums.messages import MessagesEnum
@@ -65,10 +68,17 @@ def alive():
6568
description: Success response
6669
content:
6770
application/json:
68-
schema: AliveSchema
71+
schema: HealthCheckSchema
6972
"""
70-
body = {"app": "I'm alive!"}
71-
return http_helper.create_response(body=body, status_code=200)
73+
# body = {"app": "I'm alive!"}
74+
# return http_helper.create_response(body=body, status_code=200)
75+
service = HealthCheckService()
76+
service.add_check("self", SelfConnectionHealthCheck(logger, config), [])
77+
service.add_check("mysql", MysqlConnectionHealthCheck(logger, config), ["db"])
78+
service.add_check("redis", RedisConnectionHealthCheck(logger, config), ["redis"])
79+
service.add_check("queue", SQSConnectionHealthCheck(logger, config), ["queue"])
80+
81+
return service.get_response()
7282

7383

7484
@app.route('/favicon-32x32.png')

examples/lambda_api/env/development.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ SQS_ENDPOINT=http://localstack:4566
77
SQS_LOCALSTACK=http://localstack:4566
88
APP_QUEUE=http://localstack:4566/000000000000/test-queue
99
API_SERVER=http://localhost:5000
10-
API_SERVER_DESCRIPTION=Staging server
10+
API_SERVER_DESCRIPTION=Development server
1111
LOCAL_API_SERVER=http://localhost:5000
1212
LOCAL_API_SERVER_DESCRIPTION=Development server
1313
REDIS_HOST=redis

examples/lambda_api/lambda_app/events/aws/sqs.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def connect(self, retry=False):
5252
self.logger.info('SQSEvents - Connected')
5353
self.logger.error(err)
5454
else:
55+
connection = None
5556
raise err
5657

5758
_CONNECTION = connection

examples/lambda_api/lambda_app/helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def to_dict(obj, force_str=False):
7878

7979

8080
def to_json(obj):
81-
return json.dumps(obj)
81+
return json.dumps(obj, default=str)
8282

8383

8484
def debug_mode():
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import json
2+
from datetime import timedelta
3+
4+
from lambda_app import helper
5+
from lambda_app.config import get_config
6+
from lambda_app.enums import CustomStringEnum
7+
from lambda_app.http_resources.response import ApiResponse
8+
from lambda_app.logging import get_logger
9+
from flask import Response
10+
from marshmallow import Schema, fields
11+
12+
13+
class HealthStatus(CustomStringEnum):
14+
# all available
15+
HEALTHY = 'healthy'
16+
# partial
17+
DEGRADED = 'degraded'
18+
# unavailable
19+
UNHEALTHY = 'unhealthy'
20+
21+
22+
class HealthCheckResult:
23+
def __init__(self, status, description):
24+
self.status = status if status is not None and isinstance(status, HealthStatus) else HealthStatus.UNHEALTHY
25+
self.description = description if description is not None else ""
26+
27+
@staticmethod
28+
def healthy(description):
29+
return HealthCheckResult(HealthStatus.HEALTHY, description)
30+
31+
@staticmethod
32+
def unhealthy(description):
33+
return HealthCheckResult(HealthStatus.UNHEALTHY, description)
34+
35+
@staticmethod
36+
def degraded(description):
37+
return HealthCheckResult(HealthStatus.DEGRADED, description)
38+
39+
40+
class EntrySchema(Schema):
41+
status = fields.Str(example=HealthStatus.HEALTHY.value)
42+
duration = fields.Str(example="0:00:00.013737")
43+
tags = fields.List(fields.Str(example="db"))
44+
45+
46+
class EntryData(Schema):
47+
name = fields.Nested(EntrySchema)
48+
49+
50+
class HealthCheckSchema(Schema):
51+
status = fields.Str(example=HealthStatus.HEALTHY.value)
52+
total_duration = fields.Str(example="0:00:00.013737")
53+
entries = fields.Nested(EntryData)
54+
55+
56+
class AbstractHealthCheck:
57+
def __init__(self, logger=None, config=None):
58+
# logger
59+
self.logger = logger if logger is not None else get_logger()
60+
# configurations
61+
self.config = config if config is not None else get_config()
62+
63+
def check_health(self):
64+
return HealthCheckResult.unhealthy("undefined test")
65+
66+
67+
class HealthCheckResponse(ApiResponse):
68+
69+
def __init__(self, api_request=None):
70+
super(HealthCheckResponse, self).__init__(api_request)
71+
self.status_code = 200
72+
self.status = HealthStatus.HEALTHY
73+
self.total_duration = timedelta()
74+
self.duration = timedelta()
75+
self.entries = {}
76+
77+
def get_response(self, status_code=None):
78+
79+
if not status_code:
80+
if self.status == HealthStatus.UNHEALTHY:
81+
self.status_code = 503
82+
elif self.status == HealthStatus.DEGRADED:
83+
self.status_code = 424
84+
else:
85+
self.status_code = status_code
86+
87+
headers = self.headers
88+
89+
# # update entries
90+
# self.entries["self"] = {
91+
# "status": self.self_status,
92+
# "duration": self.duration,
93+
# "tags": []
94+
# }
95+
self.entries.update(self.data)
96+
97+
body = {
98+
"status": self.status,
99+
"total_duration": self.total_duration,
100+
"entries": self.entries
101+
}
102+
103+
if 'Content-Type' in headers and headers['Content-Type'] == 'application/json':
104+
body = helper.to_json(body)
105+
return Response(response=body, status=self.status_code, headers=headers)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import os
2+
3+
import requests
4+
5+
from lambda_app.services.v1.healthcheck import AbstractHealthCheck, HealthCheckResult
6+
from lambda_app.database.mysql import get_connection
7+
from lambda_app.database.redis import get_connection as redis_get_connection
8+
from lambda_app.events.aws.sqs import SQSEvents
9+
10+
11+
class SelfConnectionHealthCheck(AbstractHealthCheck):
12+
def __init__(self, logger=None, config=None):
13+
super().__init__(logger=logger, config=config)
14+
15+
def check_health(self):
16+
result = False
17+
description = "Unable to connect"
18+
check_result = HealthCheckResult.unhealthy(description)
19+
response = None
20+
try:
21+
result = True
22+
url = os.environ["API_SERVER"] if "API_SERVER" in os.environ else None
23+
url = url + "/docs"
24+
self.logger.info("requesting url: {}".format(url))
25+
response = requests.get(url)
26+
if response:
27+
if response.status_code == 200:
28+
result = True
29+
description = "Connection successful"
30+
else:
31+
result = False
32+
description = "Something wrong"
33+
check_result = HealthCheckResult.degraded(description)
34+
else:
35+
raise Exception("Unable to connect")
36+
except Exception as err:
37+
self.logger.error(err)
38+
39+
if result:
40+
check_result = HealthCheckResult.healthy(description)
41+
return check_result
42+
43+
44+
class MysqlConnectionHealthCheck(AbstractHealthCheck):
45+
def __init__(self, logger=None, config=None, mysql_connection=None):
46+
super().__init__(logger=logger, config=config)
47+
# database connection
48+
self.mysql_connection = mysql_connection if mysql_connection is not None else get_connection()
49+
50+
def check_health(self):
51+
result = False
52+
description = "Unable to connect"
53+
check_result = HealthCheckResult.unhealthy(description)
54+
55+
try:
56+
if self.mysql_connection:
57+
self.mysql_connection.connect()
58+
self.mysql_connection.ping()
59+
result = True
60+
description = "Connection successful"
61+
else:
62+
raise Exception("mysql_connection is None")
63+
except Exception as err:
64+
self.logger.error(err)
65+
66+
if result:
67+
check_result = HealthCheckResult.healthy(description)
68+
return check_result
69+
70+
71+
class RedisConnectionHealthCheck(AbstractHealthCheck):
72+
def __init__(self, logger=None, config=None, redis_connection=None):
73+
super().__init__(logger=logger, config=config)
74+
# database connection
75+
self.redis_connection = redis_connection if redis_connection is not None else redis_get_connection()
76+
77+
def check_health(self):
78+
result = False
79+
description = "Unable to connect"
80+
check_result = HealthCheckResult.unhealthy(description)
81+
82+
try:
83+
if self.redis_connection:
84+
result = self.redis_connection.set('connection', 'true')
85+
description = "Connection successful"
86+
else:
87+
raise Exception("redis_connection is None")
88+
except Exception as err:
89+
self.logger.error(err)
90+
91+
if result:
92+
check_result = HealthCheckResult.healthy(description)
93+
return check_result
94+
95+
96+
class SQSConnectionHealthCheck(AbstractHealthCheck):
97+
def __init__(self, logger=None, config=None, sqs_events=None):
98+
super().__init__(logger=logger, config=config)
99+
# sqs_events connection
100+
self.sqs_events = sqs_events if sqs_events is not None else SQSEvents()
101+
102+
def check_health(self):
103+
result = False
104+
description = "Unable to connect"
105+
check_result = HealthCheckResult.unhealthy(description)
106+
107+
try:
108+
if self.sqs_events:
109+
connection = self.sqs_events.connect()
110+
if connection:
111+
result = True
112+
description = "Connection successful"
113+
else:
114+
raise Exception("redis_connection is None")
115+
except Exception as err:
116+
self.logger.error(err)
117+
118+
if result:
119+
check_result = HealthCheckResult.healthy(description)
120+
return check_result
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import datetime
2+
3+
from lambda_app.config import get_config
4+
from lambda_app.logging import get_logger
5+
from lambda_app.services.v1.healthcheck import AbstractHealthCheck, HealthCheckResponse, HealthStatus
6+
7+
8+
class HealthCheckService:
9+
def __init__(self, logger=None, config=None):
10+
# logger
11+
self.logger = logger if logger is not None else get_logger()
12+
# configurations
13+
self.config = config if config is not None else get_config()
14+
# checks
15+
self.entries = []
16+
17+
def add_check(self, name: str, health_check, tags: list, args: dict = None):
18+
entry = {
19+
"name": name,
20+
"health_check": health_check,
21+
"tags": tags,
22+
"args": args
23+
}
24+
self.entries.append(entry)
25+
26+
def get_result(self):
27+
28+
service_status = HealthStatus.HEALTHY
29+
total_duration = datetime.timedelta()
30+
31+
result = {
32+
"status": service_status,
33+
"total_duration": total_duration,
34+
"entries": {}
35+
}
36+
37+
for entry in self.entries:
38+
name = entry["name"]
39+
health_check = entry["health_check"]
40+
tags = entry["tags"]
41+
args = entry["args"]
42+
status = HealthStatus.UNHEALTHY
43+
start = datetime.datetime.now()
44+
45+
try:
46+
if isinstance(health_check, AbstractHealthCheck):
47+
check = health_check.check_health()
48+
elif callable(health_check):
49+
if args and len(args) > 0:
50+
check = health_check(**args)
51+
else:
52+
check = health_check()
53+
else:
54+
check = None
55+
if check is None or check.status == HealthStatus.UNHEALTHY:
56+
status = HealthStatus.UNHEALTHY
57+
else:
58+
status = check.status
59+
60+
except Exception as err:
61+
self.logger.error(err)
62+
63+
end = datetime.datetime.now()
64+
duration = (end - start)
65+
total_duration = total_duration.__add__(duration)
66+
67+
if status != HealthStatus.HEALTHY:
68+
if service_status == HealthStatus.HEALTHY:
69+
service_status = HealthStatus.DEGRADED
70+
else:
71+
service_status = HealthStatus.UNHEALTHY
72+
73+
result["entries"][name] = {
74+
"status": status,
75+
"duration": duration,
76+
"tags": tags
77+
}
78+
79+
# update variables
80+
result["status"] = service_status
81+
result["total_duration"] = total_duration
82+
return result
83+
84+
def get_response(self):
85+
result = self.get_result()
86+
response = HealthCheckResponse()
87+
response.status = result["status"]
88+
response.total_duration = result["total_duration"]
89+
response.set_data(result["entries"])
90+
return response.get_response()

examples/lambda_api/lambda_app/services/v1/ocoren_event_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def __init__(self, logger=None, config=None, sqs_events=None):
1515
self.logger = logger if logger is not None else get_logger()
1616
# configurations
1717
self.config = config if config is not None else get_config()
18-
# database connection
18+
# sqs event service
1919
self.sqs_events = sqs_events if sqs_events is not None else SQSEvents()
2020
# queue_url
2121
self.queue = self.config.APP_QUEUE

0 commit comments

Comments
 (0)