# Get the output of my own code

**URL:** <https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365>\
**Category:** Help & Support\
**Created:** [January 24, 2024, 1:08pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365 "2024-01-24T13:08:38Z")\
**Posts on this page:** 19\
**Page:** 1

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [January 24, 2024, 1:08pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/1 "2024-01-24T13:08:38Z")

</div>

Hi everyone, I have a simple question, but maybe the answer can be tricky ?

I need when my program is running to write all of it own output in a file.

I tried for example to write the output of a simple command, but the file is empty.

How can I make sure the IO catch that ? I guess it’s something else that STDOUT.

So basically, between the moment I open and I close the log file, I want every output write into that file

```auto
logFile = File.open("test.log","w")
logWriter = IO::MultiWriter.new(STDOUT,logFile)

puts "Hello pickle"

logFile.close

```

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [January 24, 2024, 1:56pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/2 "2024-01-24T13:56:22Z")

</div>

You should consider leveraging [Log - Crystal 1.11.2](https://crystal-lang.org/api/Log.html) vs trying to do what you’re wanting to do. Alternatively, it may be easier to use some other external tool that writes all STDOUT output from your program to a file. Tho how to best do this would depend on how you run your binary.

---

<div class="post-metadata">

**Author:** ![kojix2](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/kojix2/32/1583_2.png) [@kojix2](https://forum.crystal-lang.org/u/kojix2)\
**Post date:** [January 24, 2024, 2:04pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/3 "2024-01-24T14:04:48Z")

</div>

What is the purpose of doing this? Testing?

---

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [January 24, 2024, 2:10pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/4 "2024-01-24T14:10:05Z")

</div>

My binary is run from bash, because it’s a command line program.

The purpose is, when my program run a list of tasks (basically a generated crystal script), I want at some point in that script to write into log file. But as I said at some points. Because I need really to split the output in different files

---

<div class="post-metadata">

**Author:** ![aboulafia](https://avatars.discourse-cdn.com/v4/letter/a/f19dbf/32.png) [@aboulafia](https://forum.crystal-lang.org/u/aboulafia)\
**Post date:** [January 24, 2024, 5:53pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/5 "2024-01-24T17:53:52Z")

</div>

May be I missed your point but  
if your crystal program (source file is test.cr) is

```auto
puts "Hello pickle"

```

`crystal build test.cr`

Then `./test > output_log.txt`

output\_log.txt should contain “Hello pickle”

---

<div class="post-metadata">

**Author:** ![HertzDevil](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/hertzdevil/32/1023_2.png) [@HertzDevil](https://forum.crystal-lang.org/u/HertzDevil)\
**Post date:** [January 24, 2024, 6:06pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/6 "2024-01-24T18:06:40Z")

</div>

Calling the top-level `::puts` always goes through `STDOUT`, there is no way to reassign the stream used there. You probably need to do `logWriter.puts ...` instead

---

<div class="post-metadata">

**Author:** ![robmullr](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/robmullr/32/1113_2.png) [@robmullr](https://forum.crystal-lang.org/u/robmullr)\
**Post date:** [January 24, 2024, 6:40pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/7 "2024-01-24T18:40:58Z")

</div>

Just used this in Powershell on Windows. I learned something today, thanks.

---

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [January 24, 2024, 7:07pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/8 "2024-01-24T19:07:24Z")

</div>

So after your answers, I am considering to implement a way to write log.

I am doing a test class to after try to implement that under my project.

I tried that code, but I have 2 problems. The first generated file called log1 is empty, instead of the log2.  
And I would like buy default to don’t need to open a file directly.

This class will give you an idea of what I want to do:

```auto
class Testy

    def initialize
        #I WOULD LIKE A WAY BY DEFAULT LOGFILE DONT NEED TO OPEN FILE
    
        @logFilePath = "empty"
        @logFile = File.open(@logFilePath,"w")
        @logWriter = IO::MultiWriter.new(@logFile)
    end

    def writeLog(text : String)
        @logWriter.puts text
    end

    def updateLogPath(path : String)
        @logFilePath = path
        @logFile = File.open(@logFilePath,"w")
        @logWriter = IO::MultiWriter.new(@logFile)
    end

    def start
    
        #THIS GENERATED FILE IS EMPTY
        updateLogPath("log1")
        writeLog("text1")

        updateLogPath("log2")
        writeLog("other text")

        @logFile.close
    end

end

var = Testy.new
var.start

```

---

<div class="post-metadata">

**Author:** ![kojix2](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/kojix2/32/1583_2.png) [@kojix2](https://forum.crystal-lang.org/u/kojix2)\
**Post date:** [January 24, 2024, 10:53pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/9 "2024-01-24T22:53:23Z")

</div>

I’m answering on my phone so I could be wrong, but from a quick look I don’t think the first file is closed. It would be better to open and close the file every time you change the file path, or if you switch files frequently, leave it open and close it all together at the end. I also think maybe using flush would fix it.

---

<div class="post-metadata">

**Author:** ![kojix2](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/kojix2/32/1583_2.png) [@kojix2](https://forum.crystal-lang.org/u/kojix2)\
**Post date:** [January 25, 2024, 3:29am UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/10 "2024-01-25T03:29:06Z")

</div>

```cr
def writeLog(text : String)
    @logWriter.puts text
    @logWriter.flush
end

```

This will output the text.  
But do we need to close the file?

---

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [January 25, 2024, 11:00am UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/11 "2024-01-25T11:00:51Z")

</div>

Yes I need, because I need at some point to write to another file

---

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [January 25, 2024, 11:06am UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/12 "2024-01-25T11:06:31Z")

</div>

What is the purpose of flush ? Not to sure to understand

---

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [February 11, 2024, 6:07pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/13 "2024-02-11T18:07:38Z")

</div>

So after a long thinking, I find an alternative way, I will generate a main log file, and then parse it.

But I have a question, because I have got a bug when I tried to compile the program.

```auto
def runTasksFile(logEnabled = false)

            if logEnabled
                makeLogDirectory("#{@settings.rootPath}#{ISM::Default::Path::LogsDirectory}")
                logFile = File.open("#{@settings.rootPath}#{ISM::Default::Path::LogsDirectory}#{ISM::Default::Filename::MainLog}","w")
                logWriter = IO::MultiWriter.new(STDOUT,logFile)
            end

            process = Process.run( "./#{ISM::Default::Filename::Task}",
                                    output: logEnabled ? logWriter : Process::Redirect::Inherit,
                                    error: logEnabled ? logWriter : Process::Redirect::Inherit,
                                    shell: true,
                                    chdir: "#{@settings.rootPath}#{ISM::Default::Path::RuntimeDataDirectory}")

            if logEnabled
                logFile.close
            end

            if !process.success?
                exitProgram
            end
        end

```

I have got this error:

```auto
zohran@alienware-m17-r3 ~/Documents/ISM $ crystal build Main.cr -o ism
Showing last frame. Use --error-trace for full trace.

In ISM/CommandLine.cr:1440:37

 1440 | output: logEnabled ? logWriter : Process::Redirect::Inherit,
        ^
Error: expected argument 'output' to 'Process.run' to be (IO | Process::Redirect), not (IO::MultiWriter | Process::Redirect | Nil)

Overloads are:
 - Process.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : Stdio = Redirect::Close, output : Stdio = Redirect::Close, error : Stdio = Redirect::Close, chdir : Path | String | ::Nil = nil)
 - Process.run(command : String, args = nil, env : Env = nil, clear_env : Bool = false, shell : Bool = false, input : Stdio = Redirect::Pipe, output : Stdio = Redirect::Pipe, error : Stdio = Redirect::Pipe, chdir : Path | String | ::Nil = nil, &)

```

How can I fix that ?

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [February 11, 2024, 6:10pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/14 "2024-02-11T18:10:25Z")

</div>

If i had to guess it’s because it doesn’t like that `logWriter` can be nil. I’d maybe refactor it to be like:

```crystal
log_writer = if logEnabled
               makeLogDirectory("#{@settings.rootPath}#{ISM::Default::Path::LogsDirectory}")
               logFile = File.open("#{@settings.rootPath}#{ISM::Default::Path::LogsDirectory}#{ISM::Default::Filename::MainLog}", "w")
               IO::MultiWriter.new(STDOUT, logFile)
             else
               Process::Redirect::Inherit
             end

```

Then use `log_writer` for input and output vs your current ternary.

---

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [February 11, 2024, 6:18pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/15 "2024-02-11T18:18:18Z")

</div>

I didn’t know we can do that O\_O

But I have then a new error:

```auto
zohran@alienware-m17-r3 ~/Documents/ISM $ crystal build Main.cr -o ism
Showing last frame. Use --error-trace for full trace.

In ISM/CommandLine.cr:1450:25

 1450 | logFile.close
                ^----
Error: undefined method 'close' for Nil (compile-time type is (File | Nil))

Did you mean 'clone'?

```

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [February 11, 2024, 6:26pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/16 "2024-02-11T18:26:52Z")

</div>

Right, `logFile` is nilable, so you need to handle that case. E.g. maybe just do a `if logEnabled && logFile`

---

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [February 11, 2024, 6:52pm UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/17 "2024-02-11T18:52:49Z")

</div>

I decided to open in all case a log file anyway, because it’s not a problem if it generate just an empty log, this file will be generated most of the time so …

```auto
def runTasksFile(logEnabled = false)

            makeLogDirectory("#{@settings.rootPath}#{ISM::Default::Path::LogsDirectory}")
            logFile = File.open("#{@settings.rootPath}#{ISM::Default::Path::LogsDirectory}#{ISM::Default::Filename::MainLog}","w")

            if logEnabled
                logWriter = IO::MultiWriter.new(STDOUT,logFile)
            else
                logWriter = Process::Redirect::Inherit
            end

            process = Process.run( "./#{ISM::Default::Filename::Task}",
                                    output: logWriter,
                                    error: logWriter,
                                    shell: true,
                                    chdir: "#{@settings.rootPath}#{ISM::Default::Path::RuntimeDataDirectory}")

            logFile.close

            if !process.success?
                exitProgram
            end
        end

```

But now I have a new problem. I did a method to get that file content when I want and to move the content to an other file, and then clear the content.

But look like it don’t work properly. It split just the first time, and not all of the file:

```auto
def splitLogFile(destinationPath : String)
            makeLogDirectory(destinationPath[0..destinationPath.rindex("/")])

            logData = File.read("#{@settings.rootPath}#{ISM::Default::Path::LogsDirectory}#{ISM::Default::Filename::MainLog}")

            File.write(destinationPath, logData)

            File.write("#{@settings.rootPath}#{ISM::Default::Path::LogsDirectory}#{ISM::Default::Filename::MainLog}", "")
        end

```

Because I need to clear the content, even the main task is already writing inside, and without closing it.

Do you understand ?

Maybe I have to check first is everything is write before I clear ?

---

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [February 12, 2024, 5:52am UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/18 "2024-02-12T05:52:47Z")

</div>

I think I realize the problem. I thought when you use IO:MultiWriter, it will send in live the data. But look like there is a delay

---

<div class="post-metadata">

**Author:** ![Fulgurance](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/fulgurance/32/1463_2.png) [@Fulgurance](https://forum.crystal-lang.org/u/Fulgurance)\
**Post date:** [February 12, 2024, 10:56am UTC](https://forum.crystal-lang.org/t/get-the-output-of-my-own-code/6365/19 "2024-02-12T10:56:22Z")

</div>

So basically I have a crazy question, if I have already a File.write running in my program, is it still possible from another crystal script at the same time to open the same file and erase a part of the file, but still the first call continue to write inside?

An other question in case it’s not possible (or maybe a bad idea) :

Is it possible when I use the MultiWriter to redirect the output in a string format and store it in a variable?(and then I can parse it)

Like that example in the doc I guess?

```auto
io1 = IO::Memory.new
io2 = IO::Memory.new
writer = IO::MultiWriter.new(io1, io2)
writer.puts "foo bar"
io1.to_s # => "foo bar\n"
io2.to_s # => "foo bar\n"

```
