@@ -24,15 +24,19 @@ package logging
2424
2525import (
2626 "bytes"
27+ "context"
2728 "encoding/json"
2829 "fmt"
2930 "net/http"
3031 "os"
3132 "time"
3233
34+ "nhooyr.io/websocket"
35+
3336 "github.com/ansys/aali-sharedtypes/pkg/config"
3437 "go.uber.org/zap"
3538 "go.uber.org/zap/zapcore"
39+ "google.golang.org/grpc/metadata"
3640)
3741
3842///////////////////////////////////
@@ -765,3 +769,148 @@ func writeStringToFile(filename string, data string) error {
765769 _ , err = fmt .Fprintln (file , data )
766770 return err
767771}
772+
773+ ///////////////////////////////////
774+ // Log Context metadata functions
775+ ///////////////////////////////////
776+
777+ // CreateMetaDataFromCtx creates gRPC metadata from the given ContextMap and attaches it to the provided context.
778+ //
779+ // Parameters:
780+ // - ctx: the logging context map containing metadata values
781+ // - ctxWithCancel: the gRPC context to which the metadata will be attached
782+ //
783+ // Returns:
784+ // - ctxWithMetaData: the new gRPC context with the attached metadata
785+ // - err: an error if the metadata creation or attachment fails
786+ func CreateMetaDataFromCtx (ctx * ContextMap , ctxWithCancel context.Context ) (ctxWithMetaData context.Context , err error ) {
787+ // Append body with context
788+ body := []map [string ]interface {}{
789+ {},
790+ }
791+ ctx .data .Range (func (key , value interface {}) bool {
792+ body [0 ][string (key .(ContextKey ))] = value
793+ return true
794+ })
795+
796+ // Serialize struct to JSON
797+ jsonData , err := json .Marshal (& body )
798+ if err != nil {
799+ return nil , fmt .Errorf ("error serializing metadata struct to JSON: %v" , err )
800+ }
801+
802+ // Attach metadata to gRPC context
803+ md := metadata .Pairs (
804+ "aali-logging-context" , string (jsonData ),
805+ )
806+ return metadata .NewOutgoingContext (ctxWithCancel , md ), nil
807+ }
808+
809+ // CreateCtxFromMetaData creates a ContextMap from gRPC metadata in the provided context.
810+ //
811+ // Parameters:
812+ // - ctxWithMetaData: the gRPC context containing the metadata
813+ //
814+ // Returns:
815+ // - ctx: the logging context map created from the metadata
816+ // - err: an error if the metadata extraction or deserialization fails
817+ func CreateCtxFromMetaData (ctxWithMetaData context.Context ) (ctx * ContextMap , err error ) {
818+ // Create new ContextMap
819+ ctx = & ContextMap {}
820+
821+ // Extract metadata from incoming context
822+ md , ok := metadata .FromIncomingContext (ctxWithMetaData )
823+ if ! ok {
824+ return ctx , nil
825+ }
826+
827+ // Get the aali-logging-context value
828+ metadataValues := md .Get ("aali-logging-context" )
829+ if len (metadataValues ) == 0 {
830+ return ctx , nil
831+ }
832+
833+ // Take the first value (there should only be one)
834+ jsonData := metadataValues [0 ]
835+
836+ // Deserialize JSON to body
837+ var body []map [string ]interface {}
838+ err = json .Unmarshal ([]byte (jsonData ), & body )
839+ if err != nil {
840+ return nil , fmt .Errorf ("error deserializing JSON to metadata: %v" , err )
841+ }
842+
843+ // Populate the ContextMap with data from body
844+ if len (body ) > 0 && body [0 ] != nil {
845+ for key , value := range body [0 ] {
846+ ctx .data .Store (ContextKey (key ), value )
847+ }
848+ }
849+
850+ return ctx , nil
851+ }
852+
853+ // CreateDialOptionsFromCtx creates websocket dial options from the given ContextMap.
854+ //
855+ // Parameters:
856+ // - ctx: the logging context map containing metadata values
857+ //
858+ // Returns:
859+ // - opts: the websocket dial options with the attached metadata
860+ // - err: an error if the metadata creation fails
861+ func CreateDialOptionsFromCtx (ctx * ContextMap ) (opts * websocket.DialOptions , err error ) {
862+ // Append body with context
863+ body := []map [string ]interface {}{
864+ {},
865+ }
866+ ctx .data .Range (func (key , value interface {}) bool {
867+ body [0 ][string (key .(ContextKey ))] = value
868+ return true
869+ })
870+
871+ // Serialize struct to JSON
872+ jsonData , err := json .Marshal (& body )
873+ if err != nil {
874+ return nil , fmt .Errorf ("error serializing metadata struct to JSON: %v" , err )
875+ }
876+ opts = & websocket.DialOptions {
877+ HTTPHeader : http.Header {
878+ "aali-logging-context" : []string {string (jsonData )},
879+ },
880+ }
881+ return opts , nil
882+ }
883+
884+ // CreateCtxFromHeader creates a ContextMap from HTTP request headers.
885+ //
886+ // Parameters:
887+ // - request: the HTTP request containing the headers
888+ //
889+ // Returns:
890+ // - ctx: the logging context map created from the headers
891+ // - err: an error if the header extraction or deserialization fails
892+ func CreateCtxFromHeader (request * http.Request ) (ctx * ContextMap , err error ) {
893+ // Create new ContextMap
894+ ctx = & ContextMap {}
895+
896+ // Get the aali-logging-context value
897+ meta := request .Header .Get ("aali-logging-context" )
898+ if meta == "" {
899+ return ctx , nil
900+ }
901+
902+ // Deserialize JSON to body
903+ var body []map [string ]interface {}
904+ err = json .Unmarshal ([]byte (meta ), & body )
905+ if err != nil {
906+ return nil , fmt .Errorf ("error deserializing JSON to metadata: %v" , err )
907+ }
908+
909+ // Populate the ContextMap with data from body
910+ if len (body ) > 0 && body [0 ] != nil {
911+ for key , value := range body [0 ] {
912+ ctx .data .Store (ContextKey (key ), value )
913+ }
914+ }
915+ return ctx , nil
916+ }
0 commit comments