JSON logs from file not recognised as JSON

Hello,

I would like to collect JSON formatted logs from a file UTF-8 encoded continuously alimented by a Java app but Alloy escapes all quote and not recognises the line as JSON.

For exemple :

Each line in my log file is JSON, terminates with \n and interpreted correctly by jq.

My pipeline is :

local.file_match → loki.source.file → loki.process → otelcol.receiver.loki → otelcol.exporter.otlphttp

my last test :

local.file_match "logs_json" {
  path_targets = [
    {__path__ = "/MIDDLELOGS/**/*.json.log.*", __path_exclude__ = "/MIDDLELOGS/**/*.gz", service_name = "m2m"},
  ]
  ignore_older_than = "300s"
}

loki.process "process_logs" {
  forward_to = [otelcol.receiver.loki.default.receiver]

  stage.match {
    selector = "{service_name = \"m2m\"} |= \"ENTRYPOINT\""

    stage.static_labels {
      values = {"log_type" = "ENTRYPOINT"}
    }
    stage.json {
      expressions = {clienId = ""}
    }
  }
}

loki.source.file "logs_json" {
  targets = local.file_match.logs_json.targets
  forward_to = [loki.process.process_logs.receiver]
  tail_from_end = true
}

If I add drop_malformed in stage.json all logs from the file are droped.

I don’t understand why Alloy ignore the format, have you any idea of what I have missed ?

Regards,

PS : for security reasons I have hidden some fileds name and content but the structure I think you can see the structure like the reality.

Welcome to forum @tarkis

Can you please post a sample log file, obfuscating sensitive stuff but still keeping structure of it intact?

Thanks !

I will try to convice my security manager, It’s not win at the moment :slight_smile:

During waiting his response maybe you can tel me what do you want to check ? Event I can’t give you a sample, maybe I can give you details which is more easy to sanitize than a file :slight_smile:

@yosiasz I have attached an obfuscating sample.

m2m.1000.json (726.3 KB)

{“function”:“ENTRYPOINT”,“Datacenter”:“XXX”,“Host”:“https://www.services.com”,“_IP”:“00.00.00.00%1”,“Ciphersuite”:“TLS13-AES128”,“Isp”:“beautifull isp”,“Country”:“_empty”,“Reputation”:“_empty”,“ReverseInstanceID”:“None”,“ReverseUniqueID”:“None”,“ResponseCodeHttpHeader”:“Suspicious-12”}}

What is the last bracket }}

I think the visualisation from browser format wrongly the JSON.

Each log begin with

{“timestamp” :

For example :

{“timestamp”: “2025-11-19T00:00:19.505+01:00”,“level”: “INFO”,“service”: “h2h”,“application”: “h2h”,“_Id”: “239aa77f527e4f019ad74cc49524e1cd”,“mId”: “000000000000000”,“mAlias”: “mAlias”,“cAlias”: “Alias”,“message”: {“function”: “ENTRYPOINT”,“Datacenter”: “XXX”,“Host”: “https://www.services.com”,“_IP”: “00.00.00.00%1”,“Ciphersuite”: “TLS13-AES128”,“Isp”: “beautifull isp”,    “Country”: “_empty”,“Reputation”: “_empty”,“ReverseInstanceID”: “None”,    “ReverseUniqueID”: “None”,“ResponseCodeHttpHeader”: “Trusted-05”}}
1 Like

Judging from your screenshot, the logs you are sharing isn’t your actual raw log files, because it says msg={...}, so there must be another layer up.

I suspect you need to run your logs through a JSON stages, then set the msg as the log message, then deal with other labeling needs with nested JSON stages if needed. Something like (not tested):

  stage.json {
    expressions = {
      msg  = "",
    }
  }

  stage.output {
     source = "msg"
  }

msg={…] is the issue I can see in Grafana, I don’t understand what is happening and it’s why I ask help here ^^

Shared logs are updated because of security, but its the raw log file. I haven’t set anything else between the path of the logs and Alloy

I tested your suggestion, my last settings

logging {
  level = "warn"
}

otelcol.receiver.loki "default" {
  output {
    logs = [otelcol.exporter.otlphttp.logs.input]
  }
}

otelcol.exporter.otlphttp "logs" {
  client {
    endpoint = "******"
  }
  retry_on_failure {
    enabled = true
    max_elapsed_time = "2m"
  }
}

local.file_match "logs_json" {
  path_targets = [
    {
     __path__ = "/MIDDLELOGS/**/*.json.log.*",
     __path_exclude__ = "/MIDDLELOGS/**/*.gz",
     __path_exclude__ = "/MIDDLELOGS/**/*.bill",
    },
  ]
  ignore_older_than = "300s"
}

loki.process "process_logs" {
  forward_to = [otelcol.receiver.loki.default.receiver]

  stage.json {
    expressions = {msg = "",}
  }
  stage.output {
    source = "msg"
  }
}

loki.source.file "sips_logs_json" {
  targets = local.file_match.logs_json.targets
  forward_to = [loki.process.process_logs.receiver]
  tail_from_end = true
}

The issue still occured.

I continue tu find a way :slight_smile:

I have tried this :

loki.process "json_process_logs" {
  forward_to = [otelcol.receiver.loki.default.receiver]
  
  stage.static_labels {
    values = {
      host = "tesips101v",
    }
  }
  
  stage.regex {
    expression = "^(?P<has_json>\\s*\\{\\s*\")"
  }
  
  stage.template {
    source   = "__tmp_format__"
    template = "{{ if .has_json }}json{{ else }}plain{{ end }}"
    }
  
  stage.labels {
    values = {
      __tmp_format__ = "",
    }
  }
  
  stage.match {
    selector = "{__tmp_format__=\"json\"}"
    pipeline_name = "json_logs"

    stage.replace {
      expression = "({\\\")"
      replace = "{'" 
	}
    stage.replace {
      expression = "(\\\":{)"
      replace = "':{" 
	}
    stage.replace {
      expression = "(\\\"})"
      replace = "'}" 
	}
    stage.replace {
      expression = "(\\\":\\\")"
      replace = "':'" 
	}
    stage.replace {
      expression = "(\\\",\\\")"
      replace = "','" 
	}
    stage.json {
      expressions = {
        merchantId = "",
        application = "",
      }
    }
  }
}

It is look like line is treated as logfmt, the msg= is added after Alloy processings.

I still don’t know why my logs are not considering as Json :confused:

I would say, share your source raw logs (without sensitive information of course), so I can test your configuration. You don’t need to share much, just a couple of lines would do.

Also, you can mock it with bogus values too, I just want to see what your raw source logs look like.

I have built a little python for generate json logs

import json,datetime

x = {
  "name": "John",
  "age": 30,
  "city": "New York",
  "timestamp" : datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
  "messsage" : {
     "app" : "s2s",
     "type" : "event",
     "function" : "entrypoint"
  }
}

y = json.dumps(x)

print(y)

I call it with this way :

while true; do python json_logs.py | tee -a /MIDDLELOGS/*********/***********/server/amc.json.log.251202; sleep 1; done

I got :

My settings

logging {
  level = "warn"
}

otelcol.receiver.loki "default" {
  output {
    logs = [otelcol.exporter.otlphttp.logs.input]
  }
}

otelcol.exporter.otlphttp "logs" {
  client {
    endpoint = "https://********"
  }
  retry_on_failure {
    enabled = true
    max_elapsed_time = "2m"
  }
}

local.file_match "logs_json" {
  path_targets = [
    {
     __path__ = "/MIDDLELOGS/**/*.json.log.*",
     __path_exclude__ = "/MIDDLELOGS/**/*.gz",
     __path_exclude__ = "/MIDDLELOGS/**/*.bill",
    },
  ]
  ignore_older_than = "300s"
}

loki.process "json_process_logs" {
  forward_to = [otelcol.receiver.loki.default.receiver]

  stage.static_labels {
    values = {
      host = "*****",
    }
  }

  stage.regex {
    expression = "^(?P<has_json>\\s*\\{\\s*\")"
  }

  stage.template {
    source   = "__tmp_format__"
    template = "{{ if .has_json }}json{{ else }}plain{{ end }}"
    }

  stage.labels {
    values = {
      __tmp_format__ = "",
    }
  }

  stage.match {
    selector = "{__tmp_format__=\"json\"}"
    pipeline_name = "json_logs"

    stage.replace {
      expression = "(attribute_log.*$)"
      replace = ""
        }
    stage.replace {
      expression = "({\\\")"
      replace = "{\""
        }
    stage.replace {
      expression = "(\\\":{)"
      replace = "\":{"
        }
    stage.replace {
      expression = "(\\\"})"
      replace = "\"}"
        }
    stage.replace {
      expression = "(\\\":\\\")"
      replace = "\":\""
        }
    stage.replace {
      expression = "(\\\",\\\")"
      replace = "\",\""
        }
    stage.json {
      expressions = {
        merchantId = "",
        application = "",
      }
    }
  }
}

loki.source.file "logs_json" {
  targets = local.file_match.logs_json.targets
  forward_to = [loki.process.json_process_logs.receiver]
  tail_from_end = true
}