Implement api request logging to Redis Streams.

This makes it possible for external services to subscribe (through
Redis) for realtime events. E.g. a create, update or delete device event
could trigger an external synchronization.
This commit is contained in:
Orne Brocaar 2022-11-23 16:46:28 +00:00
parent 84dc8fd333
commit 963842ef55
33 changed files with 1825 additions and 170 deletions

1
api/go/Makefile vendored
View File

@ -26,6 +26,7 @@ api:
protoc ${PROTOC_ARGS} api/gateway.proto
protoc ${PROTOC_ARGS} api/frame_log.proto
protoc ${PROTOC_ARGS} api/multicast_group.proto
protoc ${PROTOC_ARGS} api/request_log.proto
integration:
protoc ${PROTOC_ARGS} integration/integration.proto

184
api/go/api/request_log.pb.go vendored Normal file
View File

@ -0,0 +1,184 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.0
// protoc v3.18.1
// source: api/request_log.proto
package api
import (
_ "github.com/chirpstack/chirpstack/api/go/v4/common"
_ "github.com/chirpstack/chirpstack/api/go/v4/gw"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
_ "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type RequestLog struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// API service name.
Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
// API method name.
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
// Metadata.
Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *RequestLog) Reset() {
*x = RequestLog{}
if protoimpl.UnsafeEnabled {
mi := &file_api_request_log_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RequestLog) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RequestLog) ProtoMessage() {}
func (x *RequestLog) ProtoReflect() protoreflect.Message {
mi := &file_api_request_log_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RequestLog.ProtoReflect.Descriptor instead.
func (*RequestLog) Descriptor() ([]byte, []int) {
return file_api_request_log_proto_rawDescGZIP(), []int{0}
}
func (x *RequestLog) GetService() string {
if x != nil {
return x.Service
}
return ""
}
func (x *RequestLog) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *RequestLog) GetMetadata() map[string]string {
if x != nil {
return x.Metadata
}
return nil
}
var File_api_request_log_proto protoreflect.FileDescriptor
var file_api_request_log_proto_rawDesc = []byte{
0x0a, 0x15, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x6c, 0x6f,
0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x1a, 0x1f, 0x67, 0x6f,
0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69,
0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13, 0x63,
0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x1a, 0x0b, 0x67, 0x77, 0x2f, 0x67, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
0xb6, 0x01, 0x0a, 0x0a, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x12, 0x18,
0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68,
0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
0x12, 0x39, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x03,
0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x4c, 0x6f, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72,
0x79, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x3b, 0x0a, 0x0d, 0x4d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14,
0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76,
0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x51, 0x0a, 0x11, 0x69, 0x6f, 0x2e, 0x63,
0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2e, 0x61, 0x70, 0x69, 0x42, 0x0a, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4c, 0x6f, 0x67, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74,
0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61,
0x63, 0x6b, 0x2f, 0x63, 0x68, 0x69, 0x72, 0x70, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x61, 0x70,
0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x34, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
file_api_request_log_proto_rawDescOnce sync.Once
file_api_request_log_proto_rawDescData = file_api_request_log_proto_rawDesc
)
func file_api_request_log_proto_rawDescGZIP() []byte {
file_api_request_log_proto_rawDescOnce.Do(func() {
file_api_request_log_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_request_log_proto_rawDescData)
})
return file_api_request_log_proto_rawDescData
}
var file_api_request_log_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_api_request_log_proto_goTypes = []interface{}{
(*RequestLog)(nil), // 0: api.RequestLog
nil, // 1: api.RequestLog.MetadataEntry
}
var file_api_request_log_proto_depIdxs = []int32{
1, // 0: api.RequestLog.metadata:type_name -> api.RequestLog.MetadataEntry
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_api_request_log_proto_init() }
func file_api_request_log_proto_init() {
if File_api_request_log_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_api_request_log_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RequestLog); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_api_request_log_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_api_request_log_proto_goTypes,
DependencyIndexes: file_api_request_log_proto_depIdxs,
MessageInfos: file_api_request_log_proto_msgTypes,
}.Build()
File_api_request_log_proto = out.File
file_api_request_log_proto_rawDesc = nil
file_api_request_log_proto_goTypes = nil
file_api_request_log_proto_depIdxs = nil
}

View File

@ -25,6 +25,7 @@ api:
protoc -I=$(GOOGLEAPIS_PATH) -I=../protobuf -I=../proto $(PROTOC_ARGS) ../proto/api/gateway.proto
protoc -I=$(GOOGLEAPIS_PATH) -I=../protobuf -I=../proto $(PROTOC_ARGS) ../proto/api/frame_log.proto
protoc -I=$(GOOGLEAPIS_PATH) -I=../protobuf -I=../proto $(PROTOC_ARGS) ../proto/api/multicast_group.proto
protoc -I=$(GOOGLEAPIS_PATH) -I=../protobuf -I=../proto $(PROTOC_ARGS) ../proto/api/request_log.proto
integration:
mkdir -p integration

33
api/grpc-web/api/request_log_pb.d.ts vendored Normal file
View File

@ -0,0 +1,33 @@
import * as jspb from 'google-protobuf'
import * as google_protobuf_timestamp_pb from 'google-protobuf/google/protobuf/timestamp_pb';
import * as common_common_pb from '../common/common_pb';
import * as gw_gw_pb from '../gw/gw_pb';
export class RequestLog extends jspb.Message {
getService(): string;
setService(value: string): RequestLog;
getMethod(): string;
setMethod(value: string): RequestLog;
getMetadataMap(): jspb.Map<string, string>;
clearMetadataMap(): RequestLog;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): RequestLog.AsObject;
static toObject(includeInstance: boolean, msg: RequestLog): RequestLog.AsObject;
static serializeBinaryToWriter(message: RequestLog, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): RequestLog;
static deserializeBinaryFromReader(message: RequestLog, reader: jspb.BinaryReader): RequestLog;
}
export namespace RequestLog {
export type AsObject = {
service: string,
method: string,
metadataMap: Array<[string, string]>,
}
}

239
api/grpc-web/api/request_log_pb.js vendored Normal file
View File

@ -0,0 +1,239 @@
// source: api/request_log.proto
/**
* @fileoverview
* @enhanceable
* @suppress {missingRequire} reports error on implicit type usages.
* @suppress {messageConventions} JS Compiler reports an error if a variable or
* field starts with 'MSG_' and isn't a translatable message.
* @public
*/
// GENERATED CODE -- DO NOT EDIT!
/* eslint-disable */
// @ts-nocheck
var jspb = require('google-protobuf');
var goog = jspb;
var global = Function('return this')();
var google_protobuf_timestamp_pb = require('google-protobuf/google/protobuf/timestamp_pb.js');
goog.object.extend(proto, google_protobuf_timestamp_pb);
var common_common_pb = require('../common/common_pb.js');
goog.object.extend(proto, common_common_pb);
var gw_gw_pb = require('../gw/gw_pb.js');
goog.object.extend(proto, gw_gw_pb);
goog.exportSymbol('proto.api.RequestLog', null, global);
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.api.RequestLog = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.api.RequestLog, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.api.RequestLog.displayName = 'proto.api.RequestLog';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.api.RequestLog.prototype.toObject = function(opt_includeInstance) {
return proto.api.RequestLog.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.api.RequestLog} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.api.RequestLog.toObject = function(includeInstance, msg) {
var f, obj = {
service: jspb.Message.getFieldWithDefault(msg, 1, ""),
method: jspb.Message.getFieldWithDefault(msg, 2, ""),
metadataMap: (f = msg.getMetadataMap()) ? f.toObject(includeInstance, undefined) : []
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.api.RequestLog}
*/
proto.api.RequestLog.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.api.RequestLog;
return proto.api.RequestLog.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.api.RequestLog} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.api.RequestLog}
*/
proto.api.RequestLog.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setService(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setMethod(value);
break;
case 3:
var value = msg.getMetadataMap();
reader.readMessage(value, function(message, reader) {
jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readString, null, "", "");
});
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.api.RequestLog.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.api.RequestLog.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.api.RequestLog} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.api.RequestLog.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getService();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
f = message.getMethod();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = message.getMetadataMap(true);
if (f && f.getLength() > 0) {
f.serializeBinary(3, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeString);
}
};
/**
* optional string service = 1;
* @return {string}
*/
proto.api.RequestLog.prototype.getService = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.api.RequestLog} returns this
*/
proto.api.RequestLog.prototype.setService = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
/**
* optional string method = 2;
* @return {string}
*/
proto.api.RequestLog.prototype.getMethod = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};
/**
* @param {string} value
* @return {!proto.api.RequestLog} returns this
*/
proto.api.RequestLog.prototype.setMethod = function(value) {
return jspb.Message.setProto3StringField(this, 2, value);
};
/**
* map<string, string> metadata = 3;
* @param {boolean=} opt_noLazyCreate Do not create the map if
* empty, instead returning `undefined`
* @return {!jspb.Map<string,string>}
*/
proto.api.RequestLog.prototype.getMetadataMap = function(opt_noLazyCreate) {
return /** @type {!jspb.Map<string,string>} */ (
jspb.Message.getMapField(this, 3, opt_noLazyCreate,
null));
};
/**
* Clears values from the map. The map will be non-null.
* @return {!proto.api.RequestLog} returns this
*/
proto.api.RequestLog.prototype.clearMetadataMap = function() {
this.getMetadataMap().clear();
return this;};
goog.object.extend(exports, proto.api);

1
api/js/Makefile vendored
View File

@ -28,6 +28,7 @@ api:
protoc ${PROTOC_GRPC_ARGS} ../proto/api/gateway.proto
protoc ${PROTOC_GRPC_ARGS} ../proto/api/frame_log.proto
protoc ${PROTOC_GRPC_ARGS} ../proto/api/multicast_group.proto
protoc ${PROTOC_GRPC_ARGS} ../proto/api/request_log.proto
integration:
protoc ${PROTOC_ARGS} ../proto/integration/integration.proto

1
api/js/api/request_log_grpc_pb.d.ts vendored Normal file
View File

@ -0,0 +1 @@
// GENERATED CODE -- NO SERVICES IN PROTO

1
api/js/api/request_log_grpc_pb.js vendored Normal file
View File

@ -0,0 +1 @@
// GENERATED CODE -- NO SERVICES IN PROTO

35
api/js/api/request_log_pb.d.ts vendored Normal file
View File

@ -0,0 +1,35 @@
// package: api
// file: api/request_log.proto
import * as jspb from "google-protobuf";
import * as google_protobuf_timestamp_pb from "google-protobuf/google/protobuf/timestamp_pb";
import * as common_common_pb from "../common/common_pb";
import * as gw_gw_pb from "../gw/gw_pb";
export class RequestLog extends jspb.Message {
getService(): string;
setService(value: string): void;
getMethod(): string;
setMethod(value: string): void;
getMetadataMap(): jspb.Map<string, string>;
clearMetadataMap(): void;
serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): RequestLog.AsObject;
static toObject(includeInstance: boolean, msg: RequestLog): RequestLog.AsObject;
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
static serializeBinaryToWriter(message: RequestLog, writer: jspb.BinaryWriter): void;
static deserializeBinary(bytes: Uint8Array): RequestLog;
static deserializeBinaryFromReader(message: RequestLog, reader: jspb.BinaryReader): RequestLog;
}
export namespace RequestLog {
export type AsObject = {
service: string,
method: string,
metadataMap: Array<[string, string]>,
}
}

226
api/js/api/request_log_pb.js vendored Normal file
View File

@ -0,0 +1,226 @@
/**
* @fileoverview
* @enhanceable
* @public
*/
// GENERATED CODE -- DO NOT EDIT!
var jspb = require('google-protobuf');
var goog = jspb;
var global = Function('return this')();
var google_protobuf_timestamp_pb = require('google-protobuf/google/protobuf/timestamp_pb.js');
var common_common_pb = require('../common/common_pb.js');
var gw_gw_pb = require('../gw/gw_pb.js');
goog.exportSymbol('proto.api.RequestLog', null, global);
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.api.RequestLog = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.api.RequestLog, jspb.Message);
if (goog.DEBUG && !COMPILED) {
proto.api.RequestLog.displayName = 'proto.api.RequestLog';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto suitable for use in Soy templates.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS.
* @param {boolean=} opt_includeInstance Whether to include the JSPB instance
* for transitional soy proto support: http://goto/soy-param-migration
* @return {!Object}
*/
proto.api.RequestLog.prototype.toObject = function(opt_includeInstance) {
return proto.api.RequestLog.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Whether to include the JSPB
* instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.api.RequestLog} msg The msg instance to transform.
* @return {!Object}
*/
proto.api.RequestLog.toObject = function(includeInstance, msg) {
var f, obj = {
service: msg.getService(),
method: msg.getMethod(),
metadataMap: (f = msg.getMetadataMap(true)) ? f.toArray() : []
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.api.RequestLog}
*/
proto.api.RequestLog.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.api.RequestLog;
return proto.api.RequestLog.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.api.RequestLog} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.api.RequestLog}
*/
proto.api.RequestLog.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setService(value);
break;
case 2:
var value = /** @type {string} */ (reader.readString());
msg.setMethod(value);
break;
case 3:
var value = msg.getMetadataMap();
reader.readMessage(value, function(message, reader) {
jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readString);
});
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Class method variant: serializes the given message to binary data
* (in protobuf wire format), writing to the given BinaryWriter.
* @param {!proto.api.RequestLog} message
* @param {!jspb.BinaryWriter} writer
*/
proto.api.RequestLog.serializeBinaryToWriter = function(message, writer) {
message.serializeBinaryToWriter(writer);
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.api.RequestLog.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
this.serializeBinaryToWriter(writer);
return writer.getResultBuffer();
};
/**
* Serializes the message to binary data (in protobuf wire format),
* writing to the given BinaryWriter.
* @param {!jspb.BinaryWriter} writer
*/
proto.api.RequestLog.prototype.serializeBinaryToWriter = function (writer) {
var f = undefined;
f = this.getService();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
f = this.getMethod();
if (f.length > 0) {
writer.writeString(
2,
f
);
}
f = this.getMetadataMap(true);
if (f && f.getLength() > 0) {
f.serializeBinary(3, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeString);
}
};
/**
* Creates a deep clone of this proto. No data is shared with the original.
* @return {!proto.api.RequestLog} The clone.
*/
proto.api.RequestLog.prototype.cloneMessage = function() {
return /** @type {!proto.api.RequestLog} */ (jspb.Message.cloneMessage(this));
};
/**
* optional string service = 1;
* @return {string}
*/
proto.api.RequestLog.prototype.getService = function() {
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 1, ""));
};
/** @param {string} value */
proto.api.RequestLog.prototype.setService = function(value) {
jspb.Message.setField(this, 1, value);
};
/**
* optional string method = 2;
* @return {string}
*/
proto.api.RequestLog.prototype.getMethod = function() {
return /** @type {string} */ (jspb.Message.getFieldProto3(this, 2, ""));
};
/** @param {string} value */
proto.api.RequestLog.prototype.setMethod = function(value) {
jspb.Message.setField(this, 2, value);
};
/**
* map<string, string> metadata = 3;
* @param {boolean=} opt_noLazyCreate Do not create the map if
* empty, instead returning `undefined`
* @return {!jspb.Map<string,string>}
*/
proto.api.RequestLog.prototype.getMetadataMap = function(opt_noLazyCreate) {
return /** @type {!jspb.Map<string,string>} */ (
jspb.Message.getMapField(this, 3, opt_noLazyCreate,
null));
};
goog.object.extend(exports, proto.api);

23
api/proto/api/request_log.proto vendored Normal file
View File

@ -0,0 +1,23 @@
syntax = "proto3";
package api;
option go_package = "github.com/chirpstack/chirpstack/api/go/v4/api";
option java_package = "io.chirpstack.api";
option java_multiple_files = true;
option java_outer_classname = "RequestLog";
import "google/protobuf/timestamp.proto";
import "common/common.proto";
import "gw/gw.proto";
message RequestLog {
// API service name.
string service = 1;
// API method name.
string method = 2;
// Metadata.
map<string, string> metadata = 3;
}

1
api/python/Makefile vendored
View File

@ -37,6 +37,7 @@ api:
$(PROTOC) ${PROTOC_ARGS} chirpstack-api/api/gateway.proto
$(PROTOC) ${PROTOC_ARGS} chirpstack-api/api/frame_log.proto
$(PROTOC) ${PROTOC_ARGS} chirpstack-api/api/multicast_group.proto
$(PROTOC) ${PROTOC_ARGS} chirpstack-api/api/request_log.proto
integration:
$(PROTOC) ${PROTOC_ARGS} chirpstack-api/integration/integration.proto

View File

@ -0,0 +1,23 @@
syntax = "proto3";
package api;
option go_package = "github.com/chirpstack/chirpstack/api/go/v4/api";
option java_package = "io.chirpstack.api";
option java_multiple_files = true;
option java_outer_classname = "RequestLog";
import "google/protobuf/timestamp.proto";
import "chirpstack-api/common/common.proto";
import "chirpstack-api/gw/gw.proto";
message RequestLog {
// API service name.
string service = 1;
// API method name.
string method = 2;
// Metadata.
map<string, string> metadata = 3;
}

View File

@ -18,3 +18,4 @@ from .tenant_pb2 import *
from .tenant_pb2_grpc import *
from .user_pb2 import *
from .user_pb2_grpc import *
from .request_log_pb2 import *

View File

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: chirpstack-api/api/request_log.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
from chirpstack_api.common import common_pb2 as chirpstack__api_dot_common_dot_common__pb2
from chirpstack_api.gw import gw_pb2 as chirpstack__api_dot_gw_dot_gw__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$chirpstack-api/api/request_log.proto\x12\x03\x61pi\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\"chirpstack-api/common/common.proto\x1a\x1a\x63hirpstack-api/gw/gw.proto\"\x8f\x01\n\nRequestLog\x12\x0f\n\x07service\x18\x01 \x01(\t\x12\x0e\n\x06method\x18\x02 \x01(\t\x12/\n\x08metadata\x18\x03 \x03(\x0b\x32\x1d.api.RequestLog.MetadataEntry\x1a/\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\x42Q\n\x11io.chirpstack.apiB\nRequestLogP\x01Z.github.com/chirpstack/chirpstack/api/go/v4/apib\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'chirpstack_api.api.request_log_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\021io.chirpstack.apiB\nRequestLogP\001Z.github.com/chirpstack/chirpstack/api/go/v4/api'
_REQUESTLOG_METADATAENTRY._options = None
_REQUESTLOG_METADATAENTRY._serialized_options = b'8\001'
_REQUESTLOG._serialized_start=143
_REQUESTLOG._serialized_end=286
_REQUESTLOG_METADATAENTRY._serialized_start=239
_REQUESTLOG_METADATAENTRY._serialized_end=286
# @@protoc_insertion_point(module_scope)

View File

@ -0,0 +1,4 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc

2
api/rust/build.rs vendored
View File

@ -141,6 +141,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
"api/gateway.proto",
"api/frame_log.proto",
"api/multicast_group.proto",
"api/frame_log.proto",
"api/request_log.proto",
],
&[
proto_dir.join("chirpstack").to_str().unwrap(),

View File

@ -0,0 +1,23 @@
syntax = "proto3";
package api;
option go_package = "github.com/chirpstack/chirpstack/api/go/v4/api";
option java_package = "io.chirpstack.api";
option java_multiple_files = true;
option java_outer_classname = "RequestLog";
import "google/protobuf/timestamp.proto";
import "common/common.proto";
import "gw/gw.proto";
message RequestLog {
// API service name.
string service = 1;
// API method name.
string method = 2;
// Metadata.
map<string, string> metadata = 3;
}

View File

@ -52,9 +52,13 @@ impl ApplicationService for Application {
let a = application::create(a).await.map_err(|e| e.status())?;
Ok(Response::new(api::CreateApplicationResponse {
let mut resp = Response::new(api::CreateApplicationResponse {
id: a.id.to_string(),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", a.id.to_string().parse().unwrap());
Ok(resp)
}
async fn get(
@ -76,7 +80,7 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::GetApplicationResponse {
let mut resp = Response::new(api::GetApplicationResponse {
application: Some(api::Application {
id: a.id.to_string(),
tenant_id: a.tenant_id.to_string(),
@ -86,7 +90,11 @@ impl ApplicationService for Application {
created_at: Some(helpers::datetime_to_prost_timestamp(&a.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&a.updated_at)),
measurement_keys,
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.id.parse().unwrap());
Ok(resp)
}
async fn update(
@ -117,7 +125,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req_app.id.parse().unwrap());
Ok(resp)
}
async fn delete(
@ -136,7 +148,11 @@ impl ApplicationService for Application {
application::delete(&app_id).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.id.parse().unwrap());
Ok(resp)
}
async fn list(
@ -169,7 +185,7 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::ListApplicationsResponse {
let mut resp = Response::new(api::ListApplicationsResponse {
total_count: count as u32,
result: results
.iter()
@ -181,7 +197,11 @@ impl ApplicationService for Application {
description: a.description.clone(),
})
.collect(),
}))
});
resp.metadata_mut()
.insert("x-log-tenant_id", req.tenant_id.parse().unwrap());
Ok(resp)
}
async fn list_integrations(
@ -226,10 +246,14 @@ impl ApplicationService for Application {
kind: api::IntegrationKind::MqttGlobal.into(),
});
Ok(Response::new(api::ListIntegrationsResponse {
let mut resp = Response::new(api::ListIntegrationsResponse {
total_count: (result.len() + 1) as u32,
result: items,
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_http_integration(
@ -271,7 +295,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_http_integration(
@ -293,7 +323,7 @@ impl ApplicationService for Application {
.map_err(|e| e.status())?;
if let application::IntegrationConfiguration::Http(conf) = &i.configuration {
Ok(Response::new(api::GetHttpIntegrationResponse {
let mut resp = Response::new(api::GetHttpIntegrationResponse {
integration: Some(api::HttpIntegration {
application_id: app_id.to_string(),
headers: conf.headers.clone(),
@ -304,7 +334,11 @@ impl ApplicationService for Application {
.into(),
event_endpoint_url: conf.event_endpoint_url.clone(),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal("Integration has no Http configuration"))
}
@ -347,7 +381,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_http_integration(
@ -368,7 +408,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_influx_db_integration(
@ -414,7 +458,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_influx_db_integration(
@ -436,7 +486,7 @@ impl ApplicationService for Application {
.map_err(|e| e.status())?;
if let application::IntegrationConfiguration::InfluxDb(conf) = &i.configuration {
Ok(Response::new(api::GetInfluxDbIntegrationResponse {
let mut resp = Response::new(api::GetInfluxDbIntegrationResponse {
integration: Some(api::InfluxDbIntegration {
application_id: app_id.to_string(),
endpoint: conf.endpoint.clone(),
@ -450,7 +500,11 @@ impl ApplicationService for Application {
organization: conf.organization.clone(),
bucket: conf.bucket.clone(),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal(
"Integration has no InfluxDb configuration",
@ -499,7 +553,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_influx_db_integration(
@ -520,7 +580,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_things_board_integration(
@ -557,7 +621,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_things_board_integration(
@ -579,12 +649,16 @@ impl ApplicationService for Application {
.map_err(|e| e.status())?;
if let application::IntegrationConfiguration::ThingsBoard(conf) = &i.configuration {
Ok(Response::new(api::GetThingsBoardIntegrationResponse {
let mut resp = Response::new(api::GetThingsBoardIntegrationResponse {
integration: Some(api::ThingsBoardIntegration {
application_id: app_id.to_string(),
server: conf.server.clone(),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal(
"Integration has no ThingsBoard configuration",
@ -624,7 +698,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_things_board_integration(
@ -645,7 +725,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_my_devices_integration(
@ -680,7 +764,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_my_devices_integration(
@ -702,12 +792,16 @@ impl ApplicationService for Application {
.map_err(|e| e.status())?;
if let application::IntegrationConfiguration::MyDevices(conf) = &i.configuration {
Ok(Response::new(api::GetMyDevicesIntegrationResponse {
let mut resp = Response::new(api::GetMyDevicesIntegrationResponse {
integration: Some(api::MyDevicesIntegration {
application_id: app_id.to_string(),
endpoint: conf.endpoint.clone(),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal(
"Integration has no MyDevices configuration",
@ -747,7 +841,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_my_devices_integration(
@ -768,7 +868,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_lora_cloud_integration(
@ -832,7 +936,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_lora_cloud_integration(
@ -856,7 +966,7 @@ impl ApplicationService for Application {
if let application::IntegrationConfiguration::LoraCloud(conf) = &i.configuration {
let mgs = &conf.modem_geolocation_services;
Ok(Response::new(api::GetLoraCloudIntegrationResponse {
let mut resp = Response::new(api::GetLoraCloudIntegrationResponse {
integration: Some(api::LoraCloudIntegration {
application_id: app_id.to_string(),
modem_geolocation_services: Some(api::LoraCloudModemGeolocationServices {
@ -877,7 +987,11 @@ impl ApplicationService for Application {
geolocation_wifi_payload_field: mgs.geolocation_wifi_payload_field.clone(),
}),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal(
"Integration has no LoraCloud configuration",
@ -946,7 +1060,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_lora_cloud_integration(
@ -967,7 +1087,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_gcp_pub_sub_integration(
@ -1005,7 +1129,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_gcp_pub_sub_integration(
@ -1027,7 +1157,7 @@ impl ApplicationService for Application {
.map_err(|e| e.status())?;
if let application::IntegrationConfiguration::GcpPubSub(conf) = &i.configuration {
Ok(Response::new(api::GetGcpPubSubIntegrationResponse {
let mut resp = Response::new(api::GetGcpPubSubIntegrationResponse {
integration: Some(api::GcpPubSubIntegration {
application_id: app_id.to_string(),
encoding: conf.encoding,
@ -1035,7 +1165,11 @@ impl ApplicationService for Application {
project_id: conf.project_id.clone(),
topic_name: conf.topic_name.clone(),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal(
"Integration has no GcpPubSub configuration",
@ -1078,7 +1212,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_gcp_pub_sub_integration(
@ -1099,7 +1239,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_aws_sns_integration(
@ -1138,7 +1282,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_aws_sns_integration(
@ -1160,7 +1310,7 @@ impl ApplicationService for Application {
.map_err(|e| e.status())?;
if let application::IntegrationConfiguration::AwsSns(conf) = &i.configuration {
Ok(Response::new(api::GetAwsSnsIntegrationResponse {
let mut resp = Response::new(api::GetAwsSnsIntegrationResponse {
integration: Some(api::AwsSnsIntegration {
application_id: app_id.to_string(),
encoding: conf.encoding,
@ -1169,7 +1319,11 @@ impl ApplicationService for Application {
secret_access_key: conf.secret_access_key.clone(),
topic_arn: conf.topic_arn.clone(),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal("Integration has no AwsSns configuration"))
}
@ -1211,7 +1365,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_aws_sns_integration(
@ -1232,7 +1392,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_azure_service_bus_integration(
@ -1269,7 +1433,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_azure_service_bus_integration(
@ -1292,14 +1462,18 @@ impl ApplicationService for Application {
.map_err(|e| e.status())?;
if let application::IntegrationConfiguration::AzureServiceBus(conf) = &i.configuration {
Ok(Response::new(api::GetAzureServiceBusIntegrationResponse {
let mut resp = Response::new(api::GetAzureServiceBusIntegrationResponse {
integration: Some(api::AzureServiceBusIntegration {
application_id: app_id.to_string(),
encoding: conf.encoding,
connection_string: conf.connection_string.clone(),
publish_name: conf.publish_name.clone(),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal(
"Integration has no AzureServiceBus configuration",
@ -1341,7 +1515,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_azure_service_bus_integration(
@ -1362,7 +1542,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_pilot_things_integration(
@ -1398,7 +1582,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_pilot_things_integration(
@ -1420,13 +1610,17 @@ impl ApplicationService for Application {
.map_err(|e| e.status())?;
if let application::IntegrationConfiguration::PilotThings(conf) = &i.configuration {
Ok(Response::new(api::GetPilotThingsIntegrationResponse {
let mut resp = Response::new(api::GetPilotThingsIntegrationResponse {
integration: Some(api::PilotThingsIntegration {
application_id: app_id.to_string(),
server: conf.server.clone(),
token: conf.token.clone(),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal(
"Integration has no PilotThings configuration",
@ -1467,7 +1661,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_pilot_things_integration(
@ -1488,7 +1688,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_ifttt_integration(
@ -1527,7 +1731,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn get_ifttt_integration(
@ -1549,13 +1759,17 @@ impl ApplicationService for Application {
.map_err(|e| e.status())?;
if let application::IntegrationConfiguration::Ifttt(conf) = &i.configuration {
Ok(Response::new(api::GetIftttIntegrationResponse {
let mut resp = Response::new(api::GetIftttIntegrationResponse {
integration: Some(api::IftttIntegration {
application_id: app_id.to_string(),
key: conf.key.clone(),
uplink_values: conf.uplink_values.to_vec(),
}),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
} else {
Err(Status::internal("Integration has no Ifttt configuration"))
}
@ -1597,7 +1811,13 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-application_id",
req_int.application_id.parse().unwrap(),
);
Ok(resp)
}
async fn delete_ifttt_integration(
@ -1618,7 +1838,11 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn generate_mqtt_integration_client_certificate(
@ -1643,14 +1867,16 @@ impl ApplicationService for Application {
.await
.map_err(|e| e.status())?;
Ok(Response::new(
api::GenerateMqttIntegrationClientCertificateResponse {
ca_cert,
tls_cert: cert,
tls_key: key,
expires_at: Some(ttl.into()),
},
))
let mut resp = Response::new(api::GenerateMqttIntegrationClientCertificateResponse {
ca_cert,
tls_cert: cert,
tls_key: key,
expires_at: Some(ttl.into()),
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
}

View File

@ -61,6 +61,7 @@ impl DeviceService for Device {
name: req_d.name.clone(),
description: req_d.description.clone(),
skip_fcnt_check: req_d.skip_fcnt_check,
is_disabled: req_d.is_disabled,
tags: fields::KeyValue::new(req_d.tags.clone()),
variables: fields::KeyValue::new(req_d.variables.clone()),
..Default::default()
@ -68,7 +69,15 @@ impl DeviceService for Device {
let _ = device::create(d).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req_d.dev_eui.parse().unwrap());
resp.metadata_mut().insert(
"x-log-is_disabled",
req_d.is_disabled.to_string().parse().unwrap(),
);
Ok(resp)
}
async fn get(
@ -87,7 +96,7 @@ impl DeviceService for Device {
let d = device::get(&dev_eui).await.map_err(|e| e.status())?;
Ok(Response::new(api::GetDeviceResponse {
let mut resp = Response::new(api::GetDeviceResponse {
device: Some(api::Device {
dev_eui: d.dev_eui.to_string(),
name: d.name.clone(),
@ -116,7 +125,11 @@ impl DeviceService for Device {
}),
false => None,
},
}))
});
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn update(
@ -175,7 +188,15 @@ impl DeviceService for Device {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req_d.dev_eui.parse().unwrap());
resp.metadata_mut().insert(
"x-log-is_disabled",
req_d.is_disabled.to_string().parse().unwrap(),
);
Ok(resp)
}
async fn delete(
@ -193,7 +214,12 @@ impl DeviceService for Device {
.await?;
device::delete(&dev_eui).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn list(
@ -235,7 +261,7 @@ impl DeviceService for Device {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::ListDevicesResponse {
let mut resp = Response::new(api::ListDevicesResponse {
total_count: count as u32,
result: items
.iter()
@ -264,7 +290,11 @@ impl DeviceService for Device {
},
})
.collect(),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn create_keys(
@ -298,7 +328,12 @@ impl DeviceService for Device {
};
let _ = device_keys::create(dk).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req_dk.dev_eui.parse().unwrap());
Ok(resp)
}
async fn get_keys(
@ -317,7 +352,7 @@ impl DeviceService for Device {
let dk = device_keys::get(&dev_eui).await.map_err(|e| e.status())?;
Ok(Response::new(api::GetDeviceKeysResponse {
let mut resp = Response::new(api::GetDeviceKeysResponse {
device_keys: Some(api::DeviceKeys {
dev_eui: dk.dev_eui.to_string(),
nwk_key: dk.nwk_key.to_string(),
@ -325,7 +360,11 @@ impl DeviceService for Device {
}),
created_at: Some(helpers::datetime_to_prost_timestamp(&dk.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&dk.updated_at)),
}))
});
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn update_keys(
@ -363,7 +402,11 @@ impl DeviceService for Device {
};
let _ = device_keys::update(dk).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req_dk.dev_eui.parse().unwrap());
Ok(resp)
}
async fn delete_keys(
@ -383,7 +426,12 @@ impl DeviceService for Device {
device_keys::delete(&dev_eui)
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn flush_dev_nonces(
@ -403,7 +451,12 @@ impl DeviceService for Device {
device_keys::set_dev_nonces(&dev_eui, &Vec::new())
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn activate(
@ -469,7 +522,11 @@ impl DeviceService for Device {
.map_err(|e| e.status())?;
}
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req_da.dev_eui.parse().unwrap());
Ok(resp)
}
async fn deactivate(
@ -493,7 +550,11 @@ impl DeviceService for Device {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn get_activation(
@ -524,7 +585,7 @@ impl DeviceService for Device {
},
};
Ok(Response::new(api::GetDeviceActivationResponse {
let mut resp = Response::new(api::GetDeviceActivationResponse {
device_activation: Some(api::DeviceActivation {
dev_eui: hex::encode(&ds.dev_eui),
dev_addr: hex::encode(&ds.dev_addr),
@ -539,7 +600,11 @@ impl DeviceService for Device {
n_f_cnt_down: ds.n_f_cnt_down,
a_f_cnt_down: ds.a_f_cnt_down,
}),
}))
});
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn get_random_dev_addr(
@ -664,7 +729,11 @@ impl DeviceService for Device {
}
}
Ok(Response::new(out))
let mut resp = Response::new(out);
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn get_link_metrics(
@ -906,7 +975,11 @@ impl DeviceService for Device {
}),
};
Ok(Response::new(out))
let mut resp = Response::new(out);
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn enqueue(
@ -960,9 +1033,13 @@ impl DeviceService for Device {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::EnqueueDeviceQueueItemResponse {
let mut resp = Response::new(api::EnqueueDeviceQueueItemResponse {
id: qi.id.to_string(),
}))
});
resp.metadata_mut()
.insert("x-log-dev_eui", req_qi.dev_eui.parse().unwrap());
Ok(resp)
}
async fn flush_queue(
@ -983,7 +1060,11 @@ impl DeviceService for Device {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn get_queue(
@ -1004,7 +1085,7 @@ impl DeviceService for Device {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::GetDeviceQueueItemsResponse {
let mut resp = Response::new(api::GetDeviceQueueItemsResponse {
total_count: items.len() as u32,
result: items
.iter()
@ -1022,7 +1103,11 @@ impl DeviceService for Device {
},
})
.collect(),
}))
});
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
}

View File

@ -91,9 +91,15 @@ impl DeviceProfileService for DeviceProfile {
dp = device_profile::create(dp).await.map_err(|e| e.status())?;
Ok(Response::new(api::CreateDeviceProfileResponse {
let mut resp = Response::new(api::CreateDeviceProfileResponse {
id: dp.id.to_string(),
}))
});
resp.metadata_mut().insert(
"x-log-device_profile_id",
dp.id.to_string().parse().unwrap(),
);
Ok(resp)
}
async fn get(
@ -112,7 +118,7 @@ impl DeviceProfileService for DeviceProfile {
let dp = device_profile::get(&dp_id).await.map_err(|e| e.status())?;
Ok(Response::new(api::GetDeviceProfileResponse {
let mut resp = Response::new(api::GetDeviceProfileResponse {
device_profile: Some(api::DeviceProfile {
id: dp.id.to_string(),
tenant_id: dp.tenant_id.to_string(),
@ -158,7 +164,11 @@ impl DeviceProfileService for DeviceProfile {
}),
created_at: Some(helpers::datetime_to_prost_timestamp(&dp.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&dp.updated_at)),
}))
});
resp.metadata_mut()
.insert("x-log-device_profile_id", req.id.parse().unwrap());
Ok(resp)
}
async fn update(
@ -228,7 +238,11 @@ impl DeviceProfileService for DeviceProfile {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-device_profile_id", req_dp.id.parse().unwrap());
Ok(resp)
}
async fn delete(
@ -248,7 +262,12 @@ impl DeviceProfileService for DeviceProfile {
device_profile::delete(&dp_id)
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-device_profile_id", req.id.parse().unwrap());
Ok(resp)
}
async fn list(
@ -281,7 +300,7 @@ impl DeviceProfileService for DeviceProfile {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::ListDeviceProfilesResponse {
let mut resp = Response::new(api::ListDeviceProfilesResponse {
total_count: count as u32,
result: items
.iter()
@ -298,7 +317,11 @@ impl DeviceProfileService for DeviceProfile {
supports_class_c: dp.supports_class_c,
})
.collect(),
}))
});
resp.metadata_mut()
.insert("x-log-tenant_id", req.tenant_id.parse().unwrap());
Ok(resp)
}
async fn list_adr_algorithms(

View File

@ -66,7 +66,11 @@ impl GatewayService for Gateway {
let _ = gateway::create(gw).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-gateway_id", req_gw.gateway_id.parse().unwrap());
Ok(resp)
}
async fn get(
@ -85,7 +89,7 @@ impl GatewayService for Gateway {
let gw = gateway::get(&gw_id).await.map_err(|e| e.status())?;
Ok(Response::new(api::GetGatewayResponse {
let mut resp = Response::new(api::GetGatewayResponse {
gateway: Some(api::Gateway {
gateway_id: gw.gateway_id.to_string(),
name: gw.name,
@ -106,7 +110,11 @@ impl GatewayService for Gateway {
.last_seen_at
.as_ref()
.map(helpers::datetime_to_prost_timestamp),
}))
});
resp.metadata_mut()
.insert("x-log-gateway_id", req.gateway_id.parse().unwrap());
Ok(resp)
}
async fn update(
@ -147,7 +155,11 @@ impl GatewayService for Gateway {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-gateway_id", req_gw.gateway_id.parse().unwrap());
Ok(resp)
}
async fn delete(
@ -165,7 +177,12 @@ impl GatewayService for Gateway {
.await?;
gateway::delete(&gw_id).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-gateway_id", req.gateway_id.parse().unwrap());
Ok(resp)
}
async fn list(
@ -203,7 +220,7 @@ impl GatewayService for Gateway {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::ListGatewaysResponse {
let mut resp = Response::new(api::ListGatewaysResponse {
total_count: count as u32,
result: result
.iter()
@ -227,7 +244,13 @@ impl GatewayService for Gateway {
.map(helpers::datetime_to_prost_timestamp),
})
.collect(),
}))
});
if !req.tenant_id.is_empty() {
resp.metadata_mut()
.insert("x-log-tenant_id", req.tenant_id.parse().unwrap());
}
Ok(resp)
}
async fn generate_client_certificate(
@ -252,14 +275,16 @@ impl GatewayService for Gateway {
.await
.map_err(|e| e.status())?;
Ok(Response::new(
api::GenerateGatewayClientCertificateResponse {
ca_cert,
tls_cert: cert,
tls_key: key,
expires_at: Some(ttl.into()),
},
))
let mut resp = Response::new(api::GenerateGatewayClientCertificateResponse {
ca_cert,
tls_cert: cert,
tls_key: key,
expires_at: Some(ttl.into()),
});
resp.metadata_mut()
.insert("x-log-gateway_id", req.gateway_id.parse().unwrap());
Ok(resp)
}
async fn get_metrics(
@ -544,7 +569,11 @@ impl GatewayService for Gateway {
}),
};
Ok(Response::new(out))
let mut resp = Response::new(out);
resp.metadata_mut()
.insert("x-log-gateway_id", req.gateway_id.parse().unwrap());
Ok(resp)
}
}

View File

@ -15,15 +15,16 @@ use prometheus_client::metrics::counter::Counter;
use prometheus_client::metrics::family::Family;
use prometheus_client::metrics::histogram::Histogram;
use rust_embed::RustEmbed;
use tokio::try_join;
use tokio::{task, try_join};
use tonic::transport::Server as TonicServer;
use tonic::Code;
use tonic_reflection::server::Builder as TonicReflectionBuilder;
use tower::{Service, ServiceBuilder};
use tower_http::trace::TraceLayer;
use tracing::{event, Level};
use tracing::{error, info};
use warp::{http::header::HeaderValue, path::Tail, reply::Response, Filter, Rejection, Reply};
use chirpstack_api::api;
use chirpstack_api::api::application_service_server::ApplicationServiceServer;
use chirpstack_api::api::device_profile_service_server::DeviceProfileServiceServer;
use chirpstack_api::api::device_profile_template_service_server::DeviceProfileTemplateServiceServer;
@ -37,6 +38,7 @@ use chirpstack_api::api::user_service_server::UserServiceServer;
use super::config;
use crate::api::auth::validator;
use crate::monitoring::prometheus;
use crate::requestlog;
pub mod application;
pub mod auth;
@ -92,11 +94,7 @@ pub async fn setup() -> Result<()> {
let conf = config::get();
let addr = conf.api.bind.parse()?;
event!(
Level::INFO,
bind = conf.api.bind.as_str(),
"Setting up API interface"
);
info!(bind = %conf.api.bind, "Setting up API interface");
// Taken from the tonic hyper_warp_multiplex example:
// https://github.com/hyperium/tonic/blob/master/examples/src/hyper_warp_multiplex/server.rs#L101
@ -172,7 +170,7 @@ pub async fn setup() -> Result<()> {
.on_request(OnRequest {})
.on_response(OnResponse {}),
)
.layer(PrometheusLogger {})
.layer(ApiLogger {})
.service(tonic_service);
// HTTP service
@ -316,22 +314,22 @@ struct GrpcLabels {
status_code: String,
}
struct PrometheusLogger {}
struct ApiLogger {}
impl<S> tower::Layer<S> for PrometheusLogger {
type Service = PrometheusLoggerService<S>;
impl<S> tower::Layer<S> for ApiLogger {
type Service = ApiLoggerService<S>;
fn layer(&self, service: S) -> Self::Service {
PrometheusLoggerService { inner: service }
ApiLoggerService { inner: service }
}
}
#[derive(Debug, Clone)]
struct PrometheusLoggerService<S> {
struct ApiLoggerService<S> {
inner: S,
}
impl<S, ReqBody, ResBody> Service<http::Request<ReqBody>> for PrometheusLoggerService<S>
impl<S, ReqBody, ResBody> Service<http::Request<ReqBody>> for ApiLoggerService<S>
where
S: Service<http::Request<ReqBody>, Response = http::Response<ResBody>>,
ReqBody: http_body::Body,
@ -339,7 +337,7 @@ where
{
type Response = S::Response;
type Error = S::Error;
type Future = PrometheusLoggerResponseFuture<S::Future>;
type Future = ApiLoggerResponseFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
@ -350,7 +348,7 @@ where
let uri_parts: Vec<&str> = uri.split('/').collect();
let response_future = self.inner.call(request);
let start = Instant::now();
PrometheusLoggerResponseFuture {
ApiLoggerResponseFuture {
response_future,
start,
service: uri_parts.get(1).map(|v| v.to_string()).unwrap_or_default(),
@ -360,7 +358,7 @@ where
}
#[pin_project]
struct PrometheusLoggerResponseFuture<F> {
struct ApiLoggerResponseFuture<F> {
#[pin]
response_future: F,
start: Instant,
@ -368,7 +366,7 @@ struct PrometheusLoggerResponseFuture<F> {
method: String,
}
impl<F, ResBody, Error> Future for PrometheusLoggerResponseFuture<F>
impl<F, ResBody, Error> Future for ApiLoggerResponseFuture<F>
where
F: Future<Output = Result<http::Response<ResBody>, Error>>,
ResBody: http_body::Body,
@ -389,6 +387,8 @@ where
},
};
let status_code = Code::from_i32(status_code);
// Log to Prometheus
let labels = GrpcLabels {
service: this.service.clone(),
method: this.method.clone(),
@ -398,6 +398,32 @@ where
GRPC_HISTOGRAM
.get_or_create(&labels)
.observe(this.start.elapsed().as_secs_f64());
// Log API request to Redis
let req_log = api::RequestLog {
service: this.service.to_string(),
method: this.method.to_string(),
metadata: response
.headers()
.iter()
.filter(|(k, _)| k.as_str().starts_with("x-log-"))
.map(|(k, v)| {
(
k.as_str()
.strip_prefix("x-log-")
.unwrap_or_default()
.to_string(),
v.to_str().unwrap().to_string(),
)
})
.collect(),
};
task::spawn(async move {
if let Err(err) = requestlog::log_request(&req_log).await {
error!("Log request error, error: {}", err);
}
});
}
Poll::Ready(result)
}

View File

@ -65,9 +65,16 @@ impl MulticastGroupService for MulticastGroup {
..Default::default()
};
let mg = multicast::create(mg).await.map_err(|e| e.status())?;
Ok(Response::new(api::CreateMulticastGroupResponse {
let mut resp = Response::new(api::CreateMulticastGroupResponse {
id: mg.id.to_string(),
}))
});
resp.metadata_mut().insert(
"x-log-multicast_group_id",
mg.id.to_string().parse().unwrap(),
);
Ok(resp)
}
async fn get(
@ -86,7 +93,7 @@ impl MulticastGroupService for MulticastGroup {
let mg = multicast::get(&mg_id).await.map_err(|e| e.status())?;
Ok(Response::new(api::GetMulticastGroupResponse {
let mut resp = Response::new(api::GetMulticastGroupResponse {
multicast_group: Some(api::MulticastGroup {
id: mg.id.to_string(),
name: mg.name.clone(),
@ -110,7 +117,11 @@ impl MulticastGroupService for MulticastGroup {
}),
created_at: Some(helpers::datetime_to_prost_timestamp(&mg.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&mg.updated_at)),
}))
});
resp.metadata_mut()
.insert("x-log-multicast_group_id", req.id.parse().unwrap());
Ok(resp)
}
async fn update(
@ -153,7 +164,11 @@ impl MulticastGroupService for MulticastGroup {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-multicast_group_id", req_mg.id.parse().unwrap());
Ok(resp)
}
async fn delete(
@ -171,7 +186,12 @@ impl MulticastGroupService for MulticastGroup {
.await?;
multicast::delete(&mg_id).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-multicast_group_id", req.id.parse().unwrap());
Ok(resp)
}
async fn list(
@ -204,7 +224,7 @@ impl MulticastGroupService for MulticastGroup {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::ListMulticastGroupsResponse {
let mut resp = Response::new(api::ListMulticastGroupsResponse {
total_count: count as u32,
result: items
.iter()
@ -222,7 +242,11 @@ impl MulticastGroupService for MulticastGroup {
.into(),
})
.collect(),
}))
});
resp.metadata_mut()
.insert("x-log-application_id", req.application_id.parse().unwrap());
Ok(resp)
}
async fn add_device(
@ -243,7 +267,16 @@ impl MulticastGroupService for MulticastGroup {
multicast::add_device(&mg_id, &dev_eui)
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-multicast_group_id",
req.multicast_group_id.parse().unwrap(),
);
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn remove_device(
@ -264,7 +297,16 @@ impl MulticastGroupService for MulticastGroup {
multicast::remove_device(&mg_id, &dev_eui)
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-multicast_group_id",
req.multicast_group_id.parse().unwrap(),
);
resp.metadata_mut()
.insert("x-log-dev_eui", req.dev_eui.parse().unwrap());
Ok(resp)
}
async fn enqueue(
@ -296,9 +338,13 @@ impl MulticastGroupService for MulticastGroup {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::EnqueueMulticastGroupQueueItemResponse {
f_cnt,
}))
let mut resp = Response::new(api::EnqueueMulticastGroupQueueItemResponse { f_cnt });
resp.metadata_mut().insert(
"x-log-multicast_group_id",
req_enq.multicast_group_id.parse().unwrap(),
);
Ok(resp)
}
async fn flush_queue(
@ -319,7 +365,13 @@ impl MulticastGroupService for MulticastGroup {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut().insert(
"x-log-multicast_group_id",
req.multicast_group_id.parse().unwrap(),
);
Ok(resp)
}
async fn list_queue(
@ -352,9 +404,15 @@ impl MulticastGroupService for MulticastGroup {
}
}
Ok(Response::new(api::ListMulticastGroupQueueResponse {
let mut resp = Response::new(api::ListMulticastGroupQueueResponse {
items: deduped_items,
}))
});
resp.metadata_mut().insert(
"x-log-multicast_group_id",
req.multicast_group_id.parse().unwrap(),
);
Ok(resp)
}
}

View File

@ -53,9 +53,13 @@ impl TenantService for Tenant {
let t = tenant::create(t).await.map_err(|e| e.status())?;
Ok(Response::new(api::CreateTenantResponse {
let mut resp = Response::new(api::CreateTenantResponse {
id: t.id.to_string(),
}))
});
resp.metadata_mut()
.insert("x-log-tenant_id", t.id.to_string().parse().unwrap());
Ok(resp)
}
async fn get(
@ -74,7 +78,7 @@ impl TenantService for Tenant {
let t = tenant::get(&tenant_id).await.map_err(|e| e.status())?;
Ok(Response::new(api::GetTenantResponse {
let mut resp = Response::new(api::GetTenantResponse {
tenant: Some(api::Tenant {
id: t.id.to_string(),
name: t.name,
@ -86,7 +90,11 @@ impl TenantService for Tenant {
}),
created_at: Some(helpers::datetime_to_prost_timestamp(&t.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&t.updated_at)),
}))
});
resp.metadata_mut()
.insert("x-log-tenant_id", req.id.parse().unwrap());
Ok(resp)
}
async fn update(
@ -122,7 +130,11 @@ impl TenantService for Tenant {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-tenant_id", req_tenant.id.parse().unwrap());
Ok(resp)
}
async fn delete(
@ -141,7 +153,11 @@ impl TenantService for Tenant {
tenant::delete(&tenant_id).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-tenant_id", req.id.parse().unwrap());
Ok(resp)
}
async fn list(
@ -245,7 +261,13 @@ impl TenantService for Tenant {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-tenant_id", req_user.tenant_id.parse().unwrap());
resp.metadata_mut()
.insert("x-log-user_id", user_id.to_string().parse().unwrap());
Ok(resp)
}
async fn get_user(
@ -268,7 +290,7 @@ impl TenantService for Tenant {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::GetTenantUserResponse {
let mut resp = Response::new(api::GetTenantUserResponse {
tenant_user: Some(api::TenantUser {
tenant_id: tenant_id.to_string(),
user_id: tu.user_id.to_string(),
@ -279,7 +301,13 @@ impl TenantService for Tenant {
}),
created_at: Some(helpers::datetime_to_prost_timestamp(&tu.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&tu.updated_at)),
}))
});
resp.metadata_mut()
.insert("x-log-tenant_id", req.tenant_id.parse().unwrap());
resp.metadata_mut()
.insert("x-log-user_id", req.user_id.parse().unwrap());
Ok(resp)
}
async fn update_user(
@ -317,7 +345,13 @@ impl TenantService for Tenant {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-tenant_id", req_user.tenant_id.parse().unwrap());
resp.metadata_mut()
.insert("x-log-user_id", req_user.user_id.parse().unwrap());
Ok(resp)
}
async fn delete_user(
@ -352,7 +386,13 @@ impl TenantService for Tenant {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-tenant_id", req.tenant_id.parse().unwrap());
resp.metadata_mut()
.insert("x-log-user_id", req.user_id.parse().unwrap());
Ok(resp)
}
async fn list_users(
@ -376,7 +416,7 @@ impl TenantService for Tenant {
.await
.map_err(|e| e.status())?;
Ok(Response::new(api::ListTenantUsersResponse {
let mut resp = Response::new(api::ListTenantUsersResponse {
total_count: count as u32,
result: result
.iter()
@ -391,7 +431,11 @@ impl TenantService for Tenant {
is_gateway_admin: tu.is_gateway_admin,
})
.collect(),
}))
});
resp.metadata_mut()
.insert("x-log-tenant_id", req.tenant_id.parse().unwrap());
Ok(resp)
}
}

View File

@ -75,9 +75,13 @@ impl UserService for User {
.map_err(|e| e.status())?;
}
Ok(Response::new(api::CreateUserResponse {
let mut resp = Response::new(api::CreateUserResponse {
id: u.id.to_string(),
}))
});
resp.metadata_mut()
.insert("x-log-user_id", u.id.to_string().parse().unwrap());
Ok(resp)
}
async fn get(
@ -96,7 +100,7 @@ impl UserService for User {
let u = user::get(&user_id).await.map_err(|e| e.status())?;
Ok(Response::new(api::GetUserResponse {
let mut resp = Response::new(api::GetUserResponse {
user: Some(api::User {
id: u.id.to_string(),
is_admin: u.is_admin,
@ -106,7 +110,11 @@ impl UserService for User {
}),
created_at: Some(helpers::datetime_to_prost_timestamp(&u.created_at)),
updated_at: Some(helpers::datetime_to_prost_timestamp(&u.updated_at)),
}))
});
resp.metadata_mut()
.insert("x-log-user_id", req.id.parse().unwrap());
Ok(resp)
}
async fn update(
@ -141,7 +149,11 @@ impl UserService for User {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-user_id", req_user.id.parse().unwrap());
Ok(resp)
}
async fn delete(
@ -169,7 +181,11 @@ impl UserService for User {
user::delete(&user_id).await.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-user_id", req.id.parse().unwrap());
Ok(resp)
}
async fn list(
@ -231,7 +247,11 @@ impl UserService for User {
.await
.map_err(|e| e.status())?;
Ok(Response::new(()))
let mut resp = Response::new(());
resp.metadata_mut()
.insert("x-log-user_id", req.user_id.parse().unwrap());
Ok(resp)
}
}

View File

@ -211,6 +211,7 @@ impl Default for Scheduler {
#[serde(default)]
pub struct Monitoring {
pub bind: String,
pub api_request_log_max_history: usize,
pub meta_log_max_history: usize,
pub gateway_frame_log_max_history: usize,
pub device_frame_log_max_history: usize,
@ -230,6 +231,7 @@ impl Default for Monitoring {
fn default() -> Self {
Monitoring {
bind: "".to_string(),
api_request_log_max_history: 10,
meta_log_max_history: 10,
gateway_frame_log_max_history: 10,
device_frame_log_max_history: 10,

View File

@ -36,6 +36,7 @@ mod maccommand;
mod metalog;
mod monitoring;
mod region;
mod requestlog;
mod sensitivity;
mod storage;
#[cfg(test)]

View File

@ -0,0 +1,80 @@
use anyhow::Result;
use prost::Message;
use tokio::task;
use crate::config;
use crate::storage::{get_redis_conn, redis_key};
use chirpstack_api::api;
pub async fn log_request(pl: &api::RequestLog) -> Result<()> {
task::spawn_blocking({
let pl = pl.clone();
move || -> Result<()> {
let conf = config::get();
let mut c = get_redis_conn()?;
if conf.monitoring.api_request_log_max_history == 0 {
return Ok(());
}
let key = redis_key("api:stream:request".to_string());
let b = pl.encode_to_vec();
redis::cmd("XADD")
.arg(&key)
.arg("MAXLEN")
.arg(conf.monitoring.api_request_log_max_history)
.arg("*")
.arg("request")
.arg(&b)
.query(&mut *c)?;
Ok(())
}
})
.await?
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test;
use redis::streams::StreamReadReply;
use std::io::Cursor;
#[tokio::test]
async fn test_log_request() {
let _guard = test::prepare().await;
let pl = api::RequestLog {
service: "ap.Foo".to_string(),
method: "bar".to_string(),
metadata: [("user_id".to_string(), "foo_user".to_string())]
.iter()
.cloned()
.collect(),
};
log_request(&pl).await.unwrap();
let mut c = get_redis_conn().unwrap();
let key = redis_key("api:stream:request".to_string());
let srr: StreamReadReply = redis::cmd("XREAD")
.arg("COUNT")
.arg(1 as usize)
.arg("STREAMS")
.arg(&key)
.arg("0")
.query(&mut *c)
.unwrap();
assert_eq!(1, srr.keys.len());
assert_eq!(1, srr.keys[0].ids.len());
if let Some(redis::Value::Data(b)) = srr.keys[0].ids[0].map.get("request") {
let pl_recv = api::RequestLog::decode(&mut Cursor::new(b)).unwrap();
assert_eq!(pl, pl_recv);
} else {
panic!("No request log");
}
}
}

1
examples/request_log/go/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
request-log

View File

@ -0,0 +1,20 @@
module request-log
go 1.18
require (
github.com/chirpstack/chirpstack/api/go/v4 v4.1.0
github.com/go-redis/redis/v8 v8.11.5
google.golang.org/protobuf v1.28.1
)
require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/golang/protobuf v1.5.2 // indirect
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
google.golang.org/grpc v1.45.0 // indirect
)

View File

@ -0,0 +1,145 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chirpstack/chirpstack/api/go/v4 v4.0.2 h1:qyblQmhC66zlKFoM5gj9TjHpvCNBMZ+qt11EEpBAb6U=
github.com/chirpstack/chirpstack/api/go/v4 v4.0.2/go.mod h1:KBW7imf70O9ifrMmoFH8+dn0+MUFS1PdC5shXH7W3dI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,63 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"github.com/chirpstack/chirpstack/api/go/v4/api"
"github.com/go-redis/redis/v8"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
)
var (
server string
key string
)
func init() {
flag.StringVar(&server, "server", "localhost:6379", "Redis hostname:port")
flag.StringVar(&key, "key", "api:stream:request", "Redis Streams key to read from")
flag.Parse()
}
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: server,
})
ctx := context.Background()
lastID := "0"
for {
resp, err := rdb.XRead(ctx, &redis.XReadArgs{
Streams: []string{key, lastID},
Count: 10,
Block: 0,
}).Result()
if err != nil {
log.Fatal(err)
}
if len(resp) != 1 {
log.Fatal("Exactly one stream response is expected")
}
for _, msg := range resp[0].Messages {
lastID = msg.ID
if b, ok := msg.Values["request"].(string); ok {
var pl api.RequestLog
if err := proto.Unmarshal([]byte(b), &pl); err != nil {
log.Fatal(err)
}
fmt.Println("=== Request ===")
fmt.Println(protojson.Format(&pl))
fmt.Println("===============")
}
}
}
}