Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
956fd58
add request id to logs
balraj111 May 19, 2026
5df2722
req id implementation in controller
balraj111 May 19, 2026
5801c68
implement comprehensive request ID tracking
balraj111 May 19, 2026
29f5a97
replace klogs with zap logs
balraj111 May 20, 2026
6f9a008
migrate from klog to zap log
balraj111 May 26, 2026
d4cae4e
klog to zap migration
balraj111 May 26, 2026
31bd5b4
build issue fix
balraj111 May 26, 2026
d7babd4
build fix unused logger
balraj111 May 26, 2026
4317828
build fix
balraj111 May 26, 2026
af2f2b2
s3client bug fixes
balraj111 May 27, 2026
d3daa98
mounter build fix
balraj111 May 27, 2026
1528d9c
pkg build fix
balraj111 May 27, 2026
0e20633
build fix
balraj111 May 27, 2026
53098fc
build fix
balraj111 May 27, 2026
caf5b6e
fixing linter isuue
balraj111 May 27, 2026
230f84a
mounter rclone build fix
balraj111 May 27, 2026
ed8f27c
build fix
balraj111 Jun 10, 2026
e4920a8
fixing test result
balraj111 Jun 10, 2026
3f9c3be
build fix
balraj111 Jun 10, 2026
a46f301
build fix
balraj111 Jun 10, 2026
0557888
fixing build issue
balraj111 Jun 10, 2026
6488cbf
test case fix
balraj111 Jun 10, 2026
fe86513
build fix
balraj111 Jun 10, 2026
087d904
build fix
balraj111 Jun 10, 2026
5f8c5ab
test case fix
balraj111 Jun 10, 2026
67b4911
build fix
balraj111 Jun 10, 2026
5038ad2
major fixes
balraj111 Jun 10, 2026
de4c3b1
build fix
balraj111 Jun 10, 2026
5b2ae21
build fix
balraj111 Jun 10, 2026
4d0a816
build fix
balraj111 Jun 10, 2026
139268c
addning e2e test cases
balraj111 Jun 10, 2026
5a24adb
build fix
balraj111 Jun 10, 2026
acc2d2a
test case fixing for build
balraj111 Jun 11, 2026
c16b300
increaseing code coverage
balraj111 Jun 11, 2026
def1c6e
minor fix
balraj111 Jun 11, 2026
9353788
minor log changes
balraj111 Jun 11, 2026
e8f7e90
consistance logging
balraj111 Jun 11, 2026
440ab06
log fixes
balraj111 Jun 11, 2026
cef91ee
log fix
balraj111 Jun 11, 2026
128a45c
update logs
balraj111 Jun 13, 2026
6e2b25d
publish .33
balraj111 Jun 13, 2026
d44c98e
testing logging changes
balraj111 Jun 13, 2026
5d70021
update version to v0.12.33 for log testing
balraj111 Jun 13, 2026
a1edcbe
publish v0.12.33
balraj111 Jun 13, 2026
4a8e4dc
testing logging
balraj111 Jun 14, 2026
a5e5bf4
publish v0.12.34
balraj111 Jun 14, 2026
2e8be92
publish v0.12.35
balraj111 Jun 15, 2026
4ef87bc
publish v0.12.36
balraj111 Jun 15, 2026
8e8c953
end to end adding request id
balraj111 Jun 16, 2026
23e5631
linter fix
balraj111 Jun 16, 2026
0f73724
minor chages format
balraj111 Jun 16, 2026
c0f2edd
format related changes
balraj111 Jun 16, 2026
843aec6
publish v0.12.37
balraj111 Jun 16, 2026
d8651f7
publish 0.12.37
balraj111 Jun 16, 2026
243a305
mounter unknown requestID fix
balraj111 Jun 17, 2026
edad15b
publish 0.12.38
balraj111 Jun 17, 2026
2537430
unkonown requestid fix
balraj111 Jun 17, 2026
9224ca8
publish 0.12.39
balraj111 Jun 17, 2026
9081254
publish 0.12.39
balraj111 Jun 17, 2026
de95981
correct inconsistent logging pattern,request id in msg field
balraj111 Jun 24, 2026
8b38d0c
fix: update test to match corrected error message format without requ…
balraj111 Jun 24, 2026
ff932c7
refactor: remove service and component fields from all logs
balraj111 Jun 24, 2026
51fecc4
publish versin 0.12.47
balraj111 Jun 24, 2026
fca5bd2
update git ignore
balraj111 Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: CI & Release
on:
push:
branches:
- main
- logging-add-reqid-34
pull_request:
branches:
- main
Expand Down Expand Up @@ -113,7 +113,7 @@ jobs:

env:
IS_LATEST_RELEASE: 'true'
APP_VERSION: 1.0.5
APP_VERSION: 0.12.47

steps:
- name: Checkout Code
Expand All @@ -139,13 +139,12 @@ jobs:
/home/runner/work/ibm-object-csi-driver/ibm-object-csi-driver/cos-csi-mounter/cos-csi-mounter-${{ env.APP_VERSION }}.deb.tar.gz.sha256
/home/runner/work/ibm-object-csi-driver/ibm-object-csi-driver/cos-csi-mounter/cos-csi-mounter-${{ env.APP_VERSION }}.rpm.tar.gz
/home/runner/work/ibm-object-csi-driver/ibm-object-csi-driver/cos-csi-mounter/cos-csi-mounter-${{ env.APP_VERSION }}.rpm.tar.gz.sha256
tag_name: v1.0.5
name: v1.0.5
tag_name: 0.12.47
name: 0.12.47
body: |
## 🚀 What’s New
- Fix for rclone mount hang issue
- Add support for s3fs disable_noobj_cache flag
- Skip unmount for 'is not a mountpoint' error
- updated logging method
- introduced requestID in logs
prerelease: ${{ env.IS_LATEST_RELEASE != 'true' }}

- name: Perform CodeQL Analysis
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
bin
bin
tests
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,49 @@ Collect logs using below commands to check failure messages

1. `oc logs cos-s3-csi-controller-0 -c cos-csi-provisioner`
2. `oc logs cos-s3-csi-driver-xxx -c cos-csi-driver`

## Request ID Tracking

The IBM Object CSI Driver implements comprehensive request ID tracking for end-to-end tracing of operations. Every CSI operation is assigned a unique UUID v4 identifier that flows through all components.

### Key Features
- **Automatic Request ID Generation**: Every gRPC request gets a unique UUID v4
- **End-to-End Propagation**: Request ID flows through CSI → S3Client → Mounter → cos-csi-mounter
- **Structured Logging**: All logs include request ID in both message text and structured fields
- **Cross-Component Tracing**: Track operations across controller, node, and mounter services

### Quick Start

Filter logs by request ID:
```bash
# Get logs for specific request
kubectl logs <pod-name> -n kube-system | grep "550e8400-e29b-41d4-a716-446655440000"

# Using jq for JSON logs
kubectl logs <pod-name> -n kube-system | jq 'select(.request_id == "550e8400-...")'
```

Track complete operation flow:
```bash
# Extract request ID from initial log
REQUEST_ID=$(kubectl logs <pod-name> -n kube-system | \
jq -r 'select(.msg | contains("CreateVolume started")) | .request_id' | head -1)

# View all logs for that request
kubectl logs <pod-name> -n kube-system | jq "select(.request_id == \"$REQUEST_ID\")"
```

### Documentation
For comprehensive documentation on request ID tracking, troubleshooting, and best practices, see:
- [Request ID Tracking Guide](docs/REQUEST_ID_TRACKING.md)

### Example Log Output
```json
{
"level": "info",
"timestamp": "2024-01-15T10:30:45.123Z",
"msg": "[550e8400-e29b-41d4-a716-446655440000] CreateVolume started",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"volume_name": "pvc-abc123"
}
```
18 changes: 11 additions & 7 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"k8s.io/klog/v2"
)

// Options is the combined set of options for all operating modes.
Expand Down Expand Up @@ -60,17 +59,21 @@ func getOptions() *Options {
}

func getZapLogger() *zap.Logger {
// Prepare a new logger
// Prepare a new logger with standardized JSON format
atom := zap.NewAtomicLevel()
encoderCfg := zap.NewProductionEncoderConfig()
encoderCfg.TimeKey = "timestamp"
encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder
encoderCfg.EncodeLevel = zapcore.LowercaseLevelEncoder
encoderCfg.MessageKey = "msg"
encoderCfg.CallerKey = "caller"
encoderCfg.LevelKey = "level"

logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(encoderCfg),
zapcore.Lock(os.Stdout),
atom,
), zap.AddCaller()).With(zap.String("name", config.CSIPluginGithubName)).With(zap.String("CSIDriverName", "IBM CSI Object Driver"))
), zap.AddCaller())

atom.SetLevel(zap.InfoLevel)
return logger
Expand All @@ -91,14 +94,15 @@ func getConfigBool(envKey string, defaultConf bool, logger zap.Logger) bool {
}

func main() {
klog.InitFlags(nil)
defer klog.Flush()

logger := getZapLogger()
defer func() {
_ = logger.Sync() // #nosec G104: Best effort sync
}()

loggerLevel := zap.NewAtomicLevel()
options := getOptions()

klog.V(1).Info("Starting Server...")
logger.Info("Starting Server...")

debugTrace := getConfigBool("DEBUG_TRACE", false, *logger)
if debugTrace {
Expand Down
4 changes: 3 additions & 1 deletion cos-csi-mounter/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
NAME := cos-csi-mounter
APP_VERSION := 1.0.5
APP_VERSION := 0.12.47
BUILD_DIR := $(NAME)-$(APP_VERSION)
BIN_DIR := bin

Expand Down Expand Up @@ -107,3 +107,5 @@ clean:

packages:
packages: deb-build rpm-build tar-package clean

#testing
13 changes: 7 additions & 6 deletions cos-csi-mounter/server/fake_server.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
//go:build !unit_test
// +build !unit_test
//go:build linux
// +build linux

package main

import (
"context"
"errors"
"net"
"net/http"
Expand All @@ -17,13 +18,13 @@ type MockMounterUtils struct {
mock.Mock
}

func (m *MockMounterUtils) FuseMount(path string, mounter string, args []string) error {
argsCalled := m.Called(path, mounter, args)
func (m *MockMounterUtils) FuseMount(ctx context.Context, path string, mounter string, args []string) error {
argsCalled := m.Called(ctx, path, mounter, args)
return argsCalled.Error(0)
}

func (m *MockMounterUtils) FuseUnmount(path string) error {
argsCalled := m.Called(path)
func (m *MockMounterUtils) FuseUnmount(ctx context.Context, path string) error {
argsCalled := m.Called(ctx, path)
return argsCalled.Error(0)
}

Expand Down
3 changes: 3 additions & 0 deletions cos-csi-mounter/server/rclone.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build linux
// +build linux

package main

import (
Expand Down
3 changes: 3 additions & 0 deletions cos-csi-mounter/server/rclone_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build linux
// +build linux

package main

import (
Expand Down
3 changes: 3 additions & 0 deletions cos-csi-mounter/server/s3fs.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build linux
// +build linux

package main

import (
Expand Down
3 changes: 3 additions & 0 deletions cos-csi-mounter/server/s3fs_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build linux
// +build linux

package main

import (
Expand Down
88 changes: 65 additions & 23 deletions cos-csi-mounter/server/server.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
//go:build linux
// +build linux

package main

import (
"context"
"flag"
"fmt"
"net"
Expand All @@ -13,6 +17,7 @@ import (
"time"

"github.com/IBM/ibm-object-csi-driver/pkg/constants"
loggerPkg "github.com/IBM/ibm-object-csi-driver/pkg/logger"
mounterUtils "github.com/IBM/ibm-object-csi-driver/pkg/mounter/utils"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
Expand Down Expand Up @@ -42,17 +47,23 @@ func init() {
}

func setUpLogger() *zap.Logger {
// Prepare a new logger
// Prepare a new logger with standardized JSON format
atom := zap.NewAtomicLevel()
encoderCfg := zap.NewProductionEncoderConfig()
encoderCfg.TimeKey = "timestamp"
encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder
encoderCfg.EncodeLevel = zapcore.LowercaseLevelEncoder
encoderCfg.MessageKey = "msg"
encoderCfg.CallerKey = "caller"
encoderCfg.LevelKey = "level"

logger := zap.New(zapcore.NewCore(
zapcore.NewJSONEncoder(encoderCfg),
zapcore.Lock(os.Stdout),
atom,
), zap.AddCaller()).With(zap.String("ServiceName", "cos-csi-mounter"))
), zap.AddCaller()).With(
zap.String("service", "cos-csi-mounter"),
zap.String("component", "mounter-server"))
atom.SetLevel(zap.InfoLevel)
return logger
}
Expand Down Expand Up @@ -159,71 +170,102 @@ func main() {

func handleCosMount(mounter mounterUtils.MounterUtils, parser MounterArgsParser) gin.HandlerFunc {
return func(c *gin.Context) {
// Extract request ID from HTTP header or generate one
reqID := c.GetHeader("X-Request-ID")
if reqID == "" {
reqID = loggerPkg.GenerateRequestID()
}
log := logger.With(zap.String("request_id", reqID))

var request MountRequest

if err := c.BindJSON(&request); err != nil {
logger.Error("invalid request: ", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
log.Error("Invalid request", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("[%s] invalid request", reqID)})
return
}

logger.Info("New mount request with values:", zap.String("Bucket", request.Bucket), zap.String("Path", request.Path), zap.String("Mounter", request.Mounter), zap.Any("Args", request.Args))
log.Info("New mount request",
zap.String("bucket", request.Bucket),
zap.String("path", request.Path),
zap.String("mounter", request.Mounter),
zap.Any("args", request.Args))

if request.Mounter != constants.S3FS && request.Mounter != constants.RClone {
logger.Error("invalid mounter", zap.Any("mounter", request.Mounter))
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid mounter"})
log.Error("Invalid mounter", zap.String("mounter", request.Mounter))
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("[%s] invalid mounter", reqID)})
return
}

if request.Bucket == "" {
logger.Error("missing bucket in request")
c.JSON(http.StatusBadRequest, gin.H{"error": "missing bucket"})
log.Error("Missing bucket in request")
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("[%s] missing bucket", reqID)})
return
}

// validate mounter args
log.Debug("Parsing mounter args")
args, err := parser.Parse(request)
if err != nil {
logger.Error("failed to parse mounter args", zap.Any("mounter", request.Mounter), zap.Error(err))

c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid args for mounter: %v", err)})
log.Error("Failed to parse mounter args",
zap.String("mounter", request.Mounter), zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("[%s] invalid args for mounter: %v", reqID, err)})
return
}

err = mounter.FuseMount(request.Path, request.Mounter, args)
log.Info("Mounting bucket",
zap.String("path", request.Path),
zap.String("mounter", request.Mounter))

// Create context with request_id for end-to-end tracing
ctx := context.WithValue(c.Request.Context(), mounterUtils.RequestIDKey, reqID)
err = mounter.FuseMount(ctx, request.Path, request.Mounter, args)
if err != nil {
logger.Error("mount failed: ", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("mount failed: %v", err)})
log.Error("Mount failed", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("[%s] mount failed: %v", reqID, err)})
return
}

logger.Info("bucket mount is successful", zap.Any("bucket", request.Bucket), zap.Any("path", request.Path))
log.Info("Bucket mount successful",
zap.String("bucket", request.Bucket),
zap.String("path", request.Path))
c.JSON(http.StatusOK, gin.H{"status": "success"})
}
}

func handleCosUnmount(mounter mounterUtils.MounterUtils) gin.HandlerFunc {
return func(c *gin.Context) {
// Extract request ID from HTTP header or generate one
reqID := c.GetHeader("X-Request-ID")
if reqID == "" {
reqID = loggerPkg.GenerateRequestID()
}
log := logger.With(zap.String("request_id", reqID))

var request struct {
Path string `json:"path"`
}

if err := c.BindJSON(&request); err != nil {
logger.Error("invalid request: ", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
log.Error("Invalid request", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("[%s] invalid request", reqID)})
return
}

logger.Info("New unmount request with values: ", zap.String("Path", request.Path))
log.Info("New unmount request", zap.String("path", request.Path))

log.Info("Unmounting bucket", zap.String("path", request.Path))

err := mounter.FuseUnmount(request.Path)
// Create context with request_id for end-to-end tracing
ctx := context.WithValue(c.Request.Context(), mounterUtils.RequestIDKey, reqID)
err := mounter.FuseUnmount(ctx, request.Path)
if err != nil {
logger.Error("unmount failed: ", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("unmount failed :%v", err)})
log.Error("Unmount failed", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("[%s] unmount failed: %v", reqID, err)})
return
}

logger.Info("bucket unmount is successful", zap.Any("path", request.Path))
log.Info("Bucket unmount successful", zap.String("path", request.Path))
c.JSON(http.StatusOK, gin.H{"status": "success"})
}
}
Loading