raigons

raigons

Stream file text and parse each line information where lines are not always independent

Hi there.

For a matter of studying and practice reasons I have the following problem:

Given I have a log txt file (usually large, so that I’m using File.stream), I need to parse some lines until a pattern is found.

Example txt file:

[<info_1>] [<info_2>]: Some text information
[<info_1>] [<info_4>] : Another text information

At this point, ok, I can parse each line and extracts the information I want separately. In my solution I built a struct where I extract each one of these information and add them to that struct, like

%Info {
   origin: "<info_1>"
   something: "<info_2>"
   message: "Another text information"
},

%Info{
   origin: "<info_1>"
   something: "<info_4>"
   message: "Some text information"
}

But sometimes the log file comes like following:

[<info_1>] [<info_2>]: Some text information
[<info_1>] [<info_4>] : Another text information
but split multiline 
like this
[<info_1>] [<info_5>]: Other formatted information

At first, for those 2 lines I was not able to extract all informations I wanted, because there is no prefix. Then I used Stream.scan/3 where I could accumulate the last parsed line to the next iteration and use that to fill that information, eg., when the iteration reached but split multiline, the previous parsed data would be [<info_1>] [<info_4>] : Another text information and I would have the %Info{} all filled and could used that data to fill the missed fields.
And when the iteration reached the line like this, the previous parsed data would be:

%Info{
   origin: "<info_1>"
   something: "<info_4>"
   message: "but split multiline "
}}

because I filled the fields and it worked.

The final code was something like:

file_name
    |> File.stream!()
    |> Stream.map(&String.trim/1)
    |> Stream.filter(&reject_empty_lines/1)
    |> Stream.scan(%Info{}, &store_info(Parser.parse(&1), &2))

where &1 is the current line end &2 is the previous parsed data %Info{}.

I don’t know if I explained this clearly.

The problem with this solution is that now I wanted to make this run in parallel computations using Flow but I can’t, because of these cases where there are multilines information where I can’t get previous line information (and it is not under my control the way this is formatted).

I forget to mention that I store this data in each iteration.

At the moment, my solution using Flow is saving data with empty fields, but I set this in a separated list under a hash.

    file_name
    |> File.stream!()
    |> Flow.from_enumerable()
    |> Flow.map(&String.trim/1)
    |> Flow.filter(&reject_empty_lines/1)
    |> Flow.partition()
    |> Flow.reduce(fn -> 0 end, fn line, acc ->
      info = Parser.parse(line)
      origin = info.origin || "unmapped"
      
      #just to show I save this in a key-value 
      store_info(origin, info)
    end)

But I am not able to relate this “unmapped” information with any of those that is “mapped” (with all fields filled).

Could you give some suggestion? I thought about something like “read line and get next line until I find the pattern”, but I don’t think it is possible.

Any tips? eheh

Thank you :slight_smile:

First Post!

LostKobrakai

LostKobrakai

Wouldn’t Stream.chunk_while be a solution to merge multi line logs together?

Last Post!

LostKobrakai

LostKobrakai

"""
[1] Test
2
3
[2] Ok
[3] Ok
[4] Test
2
3
"""
|> String.split("\n")
|> Stream.chunk_while(
  [],
  fn element, acc ->
    case element do
      <<"[", rest::binary>> ->
        case acc do
          [] -> {:cont, [element]}
          _ -> {:cont, acc |> Enum.reverse() |> Enum.join("\n"), [element]}
        end

      _ ->
        {:cont, [element | acc]}
    end
  end,
  fn
    [] -> {:cont, []}
    acc -> {:cont, acc |> Enum.reverse() |> Enum.join("\n"), []}
  end
)
|> Enum.into([])
|> IO.inspect()
# [
#   "[1] Test\n2\n3", 
#   "[2] Ok", 
#   "[3] Ok", 
#   "[4] Test\n2\n3\n"
# ]

Where Next?

Popular in Questions Top

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New

Other popular topics Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54006 488
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New

We're in Beta

About us Mission Statement