Skip to content

Commit

Permalink
Sanity check (open-telemetry#136)
Browse files Browse the repository at this point in the history
* sanity check

* fix indentation

* CI

* nit

* cleanup
  • Loading branch information
reyang committed Jun 14, 2022
1 parent e8612c7 commit 7c5cee8
Show file tree
Hide file tree
Showing 14 changed files with 156 additions and 55 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,12 @@ jobs:
use-quiet-mode: 'no'
use-verbose-mode: 'yes'
config-file: '.github/.mlc_config.json'

sanity:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: run sanitycheck.py
run: python3 ./internal/tools/sanitycheck.py
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,23 +196,24 @@ Find **Protocol Buffers Descriptions** at the [`./pb` directory](./pb/README.md)

## Features

- **[Kubernetes](https://kubernetes.io):**
The app is designed to run on Kubernetes (both locally , as well as on the cloud).
- **[Docker](https://docs.docker.com):**
This forked sample can also be executed only with Docker.
- **[gRPC](https://grpc.io):**
Microservices use a high volume of gRPC calls to communicate to each other.
- **[OpenTelemetry Traces](https://opentelemetry.io):**
All services are instrumented using OpenTelemetry available instrumentation libraries.
- **[OpenTelemetry Collector](https://opentelemetry.io/docs/collector/getting-started):**
All services are instrumented and sending the generated traces to the
- **[Kubernetes](https://kubernetes.io)**: the app is designed to run on
Kubernetes (both locally , as well as on the cloud).
- **[Docker](https://docs.docker.com)**: this forked sample can also be executed
only with Docker.
- **[gRPC](https://grpc.io)**: microservices use a high volume of gRPC calls to
communicate to each other.
- **[OpenTelemetry Traces](https://opentelemetry.io)**: all services are
instrumented using OpenTelemetry available instrumentation libraries.
- **[OpenTelemetry
Collector](https://opentelemetry.io/docs/collector/getting-started)**: all
services are instrumented and sending the generated traces to the
OpenTelemetry Collector via gRPC. The received traces are then exported to the
logs and to Jaeger.
- **[Jaeger](https://www.jaegertracing.io):**
All generated traces are being sent to Jaeger.
- **Synthetic Load Generation:**
The application demo comes with a background job that creates realistic usage
patterns on the website using [Locust](https://locust.io/) load generator.
- **[Jaeger](https://www.jaegertracing.io)**: all generated traces are being
sent to Jaeger.
- **Synthetic Load Generation**: the application demo comes with a background
job that creates realistic usage patterns on the website using
[Locust](https://locust.io/) load generator.

## Demos featuring Online Boutique

Expand Down
22 changes: 11 additions & 11 deletions Requirements/architecture-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

## Summary

The OpenTelemetry Community Demo application is intended to be a showcase for
The OpenTelemetry Community Demo application is intended to be a 'showcase' for
OpenTelemetry API, SDK, and tools in a production-lite cloud native
application. The overall goal of this application is not only to provide a
canonical demo of OpenTelemetry components, but also to act as a framework for
canonical 'demo' of OpenTelemetry components, but also to act as a framework for
further customization by end-users, vendors, and other stakeholders.

### Requirements
Expand All @@ -24,43 +24,43 @@ OOB).
- Provide the OpenTelemetry community with a living artifact that
demonstrates the features and capabilities of OTel APIs, SDKs, and tools.
- Provide OpenTelemetry maintainers and WGs a platform to demonstrate new
features/concepts in the wild.
features/concepts 'in the wild'.

The following is a general description of the logical components of the demo
application. The future architecture is visualized [here](../docs/v1Graph.md).

## Main Application

The bulk of the demo app is a self-contained
microservice-based application that does some useful real-world work, such as
microservice-based application that does some useful 'real-world' work, such as
an eCommerce site. This application is composed of multiple services that
communicate with each other over gRPC and HTTP and runs on Kubernetes (or
Docker, locally).

Each service shall be instrumented with OpenTelemetry for traces, metrics, and
logs (as applicable/available).

Each service should be swappable with a service that performs the same
Each service should be 'swappable' with a service that performs the same
business logic, implementing the same gRPC endpoints, but written in a different
language/implementation. For the initial implementation of the demo, we should
focus on adding as many missing languages as possible by swapping out existing
services with implementations in un-represented languages. For future versions
we will look to add more distinct language options per service.

Each service should communicate with a feature flag service in order to
enable/disable faults that can be used to illustrate how telemetry helps solve
enable/disable 'faults' that can be used to illustrate how telemetry helps solve
problems in distributed applications.

A PHP service should be added to the main application as an admin service. A
A PHP service should be added to the main application as an 'admin service'. A
Database should be added to enable CRUD functionality on the Product Catalog.

The shippingservice should be reimplemented in Rust.
The 'shippingservice' should be reimplemented in Rust.

The currencyservice should be reimplemented in C++.
The 'currencyservice' should be reimplemented in C++.

The emailservice should be reimplemented in Ruby.
The 'emailservice' should be reimplemented in Ruby.

For future iterations, the frontend service can be extended with a mobile
For future iterations, the 'frontend' service can be extended with a mobile
application written in Swift.

## Feature Flag Component
Expand Down
2 changes: 1 addition & 1 deletion docs/v1Graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ featureflagbeservice(Flag Server):::erlang
featureflagfeservice(Flag UI/API):::erlang
featureflagstore[(Flag Store<br/>&#40Blob/DB&#41)]
featureflagfeservice --> featureflagbeservice --> featureflagstore
featureflagfeservice --> featureflagbeservice --> featureflagstore
end
classDef java fill:#b07219,color:white;
Expand Down
91 changes: 91 additions & 0 deletions internal/tools/sanitycheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env python3

import glob
import os
import sys

CR = b'\r'
CRLF = b'\r\n'
LF = b'\n'

def sanitycheck(pattern, allow_utf8 = False, allow_eol = (CRLF, LF), indent = 1):
error_count = 0

for filename in glob.glob(pattern, recursive=True):
if not os.path.isfile(filename):
continue
with open(filename, 'rb') as file:
content = file.read()
error = []
eol = None
lineno = 1
if not content:
error.append(' Empty file found')
elif content[-1] != 10: # LF
error.append(' Missing a blank line before EOF')
for line in content.splitlines(True):
if allow_utf8 and lineno == 1 and line.startswith(b'\xef\xbb\xbf'):
line = line[3:]
if any(b == 7 for b in line):
error.append(' TAB found at Ln:{} {}'.format(lineno, line))
if any(b > 127 for b in line):
error.append(' Non-ASCII character found at Ln:{} {}'.format(lineno, line))
if line[-2:] == CRLF:
if not eol:
eol = CRLF
elif eol != CRLF:
error.append(' Inconsistent line ending found at Ln:{} {}'.format(lineno, line))
line = line[:-2]
elif line[-1:] == LF:
if not eol:
eol = LF
elif eol != LF:
error.append(' Inconsistent line ending found at Ln:{} {}'.format(lineno, line))
line = line[:-1]
elif line[-1:] == CR:
error.append(' CR found at Ln:{} {}'.format(lineno, line))
line = line[:-1]
if eol:
if eol not in allow_eol:
error.append(' Line ending {} not allowed at Ln:{}'.format(eol, lineno))
break
if line.startswith(b' '):
spc_count = 0
for c in line:
if c != 32:
break
spc_count += 1
if not indent or spc_count % indent:
error.append(' {} SPC found at Ln:{} {}'.format(spc_count, lineno, line))
if line[-1:] == b' ' or line[-1:] == b'\t':
error.append(' Trailing space found at Ln:{} {}'.format(lineno, line))
lineno += 1
if error:
error_count += 1
print('{} [FAIL]'.format(filename), file=sys.stderr)
for msg in error:
print(msg, file=sys.stderr)
else:
# print('{} [PASS]'.format(filename))
pass

return error_count

retval = 0
retval += sanitycheck('**/Dockerfile', allow_eol = (LF,), indent = 2)
retval += sanitycheck('**/*.cmd', allow_eol = (CRLF,), indent = 2)
retval += sanitycheck('**/*.config', allow_utf8 = True, allow_eol = (LF,), indent = 2)
retval += sanitycheck('**/*.cs', allow_utf8 = True, allow_eol = (LF,))
retval += sanitycheck('**/*.csproj', allow_utf8 = True, allow_eol = (LF,), indent = 2)
# retval += sanitycheck('**/*.htm', allow_eol = (LF,), indent = 4)
# retval += sanitycheck('**/*.html', allow_eol = (LF,), indent = 4)
retval += sanitycheck('**/*.md', allow_eol = (LF,))
retval += sanitycheck('**/*.proj', allow_eol = (LF,), indent = 2)
retval += sanitycheck('**/*.props', allow_eol = (LF,), indent = 2)
retval += sanitycheck('**/*.py', allow_eol = (LF,), indent = 4)
retval += sanitycheck('**/*.sln', allow_utf8 = True, indent = 4)
retval += sanitycheck('**/*.targets', allow_eol = (LF,), indent = 2)
# retval += sanitycheck('**/*.xml', allow_eol = (LF,), indent = 4)
retval += sanitycheck('**/*.yml', allow_eol = (LF,), indent = 2)

sys.exit(retval)
2 changes: 1 addition & 1 deletion src/cartservice/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ static IHostBuilder CreateHostBuilder(string[] args) =>
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
});
6 changes: 3 additions & 3 deletions src/cartservice/src/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ public Startup(IConfiguration configuration)
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
{
string redisAddress = Configuration["REDIS_ADDR"];
RedisCartStore cartStore = null;
if (string.IsNullOrEmpty(redisAddress))
Expand Down Expand Up @@ -75,4 +75,4 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
});
}
}
}
}
4 changes: 2 additions & 2 deletions src/cartservice/src/cartstore/ICartStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ namespace cartservice.cartstore
public interface ICartStore
{
Task InitializeAsync();

Task AddItemAsync(string userId, string productId, int quantity);
Task EmptyCartAsync(string userId);

Task<Hipstershop.Cart> GetCartAsync(string userId);

bool Ping();
}
}
}
4 changes: 2 additions & 2 deletions src/cartservice/src/cartstore/LocalCartStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public Task EmptyCartAsync(string userId)
var eventTags = new ActivityTagsCollection();
eventTags.Add("userId", userId);
Activity.Current?.AddEvent(new ActivityEvent("EmptyCartAsync called.", default, eventTags));

userCartItems[userId] = new Hipstershop.Cart();
return Task.CompletedTask;
}
Expand All @@ -89,4 +89,4 @@ public bool Ping()
return true;
}
}
}
}
2 changes: 1 addition & 1 deletion src/cartservice/src/services/CartService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ public async override Task<Empty> EmptyCart(EmptyCartRequest request, ServerCall
return Empty;
}
}
}
}
4 changes: 2 additions & 2 deletions src/cartservice/src/services/HealthCheckService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal class HealthCheckService : HealthBase
{
private ICartStore _dependency { get; }

public HealthCheckService (ICartStore dependency)
public HealthCheckService (ICartStore dependency)
{
_dependency = dependency;
}
Expand All @@ -38,4 +38,4 @@ public override Task<HealthCheckResponse> Check(HealthCheckRequest request, Serv
});
}
}
}
}
2 changes: 1 addition & 1 deletion src/paymentservice/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ FROM base as builder
RUN apk add --update --no-cache \
python3 \
make \
g++
g++

WORKDIR /usr/src/app

Expand Down
28 changes: 14 additions & 14 deletions src/recommendationservice/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@


class CustomJsonFormatter(jsonlogger.JsonFormatter):
def add_fields(self, log_record, record, message_dict):
super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
if not log_record.get('otelTraceID'):
log_record['otelTraceID'] = trace.format_trace_id(trace.get_current_span().get_span_context().trace_id)
if not log_record.get('otelSpanID'):
log_record['otelSpanID'] = trace.format_span_id(trace.get_current_span().get_span_context().span_id)
def add_fields(self, log_record, record, message_dict):
super(CustomJsonFormatter, self).add_fields(log_record, record, message_dict)
if not log_record.get('otelTraceID'):
log_record['otelTraceID'] = trace.format_trace_id(trace.get_current_span().get_span_context().trace_id)
if not log_record.get('otelSpanID'):
log_record['otelSpanID'] = trace.format_span_id(trace.get_current_span().get_span_context().span_id)

def getJSONLogger(name):
logger = logging.getLogger(name)
handler = logging.StreamHandler(sys.stdout)
formatter = CustomJsonFormatter('%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s] - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.propagate = False
return logger
logger = logging.getLogger(name)
handler = logging.StreamHandler(sys.stdout)
formatter = CustomJsonFormatter('%(asctime)s %(levelname)s [%(name)s] [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s] - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
logger.propagate = False
return logger
4 changes: 2 additions & 2 deletions src/recommendationservice/recommendation_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def must_map_env(key: str):

# keep alive
try:
while True:
while True:
time.sleep(10000)
except KeyboardInterrupt:
server.stop(0)
server.stop(0)

0 comments on commit 7c5cee8

Please sign in to comment.