http://coldfusion.com logo
#lucee
Title
# lucee
a

Adam Cameron

03/23/2022, 12:54 PM
Hey I've been testing
SystemOutput
(https://docs.lucee.org/reference/functions/systemoutput.html) to write to stdout and stderr. Seems to be working well. I was hoping to write an test for the code, so wanted to listen to stdout and stderr as part of that. Anyone done this? I looked on S/O for a Java approach to it, and thusfar the answers have all been along the lines of "ooooh... that isn't easy, so do it this other way instead". The other ways are not helpful to me in this case (although it's good knowledge to know).
Ah, stdout and stderr also get logged to
/usr/local/lib/serverHome/logs/server.out.txt
it seems. That'll do. Now I just need to work out how to tail a file in Lucee... 😄
z

zackster

03/23/2022, 2:28 PM
you know it also can dump non simple values?
is will automatically serialize to json, i,.e.
systemOutput(getApplicationSettings())
👍 1
b

bdw429s

03/23/2022, 7:21 PM
@Adam Cameron You can wrap the Java's
System.out
print reader, but it's certainly a pain
And in fact, Lucee already wraps it internally so it can do tricky stuff like redirecting all system output 🙂
You could probably just use Lucee's setting to redirect all of the standard output of the process, but it would require custom settings in your Lucee XMl file which have no corresponding admin UI form fields
Tailing the servlet's out log (like you're doing) is probably less hair pulling.
a

Adam Cameron

03/23/2022, 9:08 PM
Cheers for that fella. I had read about the PrintStream stuff this afternoon, but thought it might be stretching my Java a bit and decided to try other options first. BUT you piqued my interest mentioning it again, so I tried my hand. What do you make of this (gist here: https://gist.github.com/adamcameron/8b0913963c768b089b48434f43d5eaac):
Copy code
import test.BaseSpec

component extends=BaseSpec {

	function run() {
		describe("Trying to capture stdout", () => {

			var fixtures = {}

			aroundEach((spec) => {
				try {
					fixtures.outFilePath = getTempDirectory() & createUuid() & ".out"
					fixtures.system = createObject("java", "java.lang.System")

					var originalOut = fixtures.system.out
					var fos = createObject("java", "java.io.FileOutputStream").init(fixtures.outFilePath)
					var ps = createObject("java", "java.io.PrintStream").init(fos, true)

					fixtures.system.setOut(ps)

					spec.body()
				} finally {
					fixtures.system.setOut(originalOut)
				}
			})

			it("captures stuff Java puts there", () => {
				var testString = "find this (Java)"
				fixtures.system.out.println(testString)

				var contents = fileRead(fixtures.outFilePath)
				expect(contents).toBe(testString & chr(10))
			})

			it("doesn't capture stuff Lucee puts there", () => {
				systemOutput("find this (Lucee)")

				var contents = fileRead(fixtures.outFilePath)

				expect(contents).toBeEmpty() // except I wish this *wasn't* the case
			})
		})
	}
}

https://i2.paste.pics/0fb1c7acd3335bfe9ee7c18e0f363eea.png

Note the Lucee one misbehaves. I guess this is because of what you say about "Lucee already wraps it internally so it can do tricky stuff". Good old Lucee "tricky stuff" making "normal stuff" not work. Or have I messed up somewhere?
b

bdw429s

03/23/2022, 9:31 PM
@Adam Cameron What you're doing is fine, but the issue is (at least from me flipping through code for the past 3 minutes) that Lucee's
systemOutput()
function doesn't just internally do this
Copy code
System.out.println( yourText )
Instead, it does this
Copy code
PrintStream stream = CFMLEngineImpl.CONSOLE_OUT;
		stream.println(string);
And that
CONSOLE_OUT
is a static variable set when the CFMLEngine is constructed like so
Copy code
public static final PrintStream CONSOLE_ERR = System.err;
	public static final PrintStream CONSOLE_OUT = System.out;
This means Lucee is going to print your string to whatever the print stream WAS back when Lucee started. Which, for the sake of discussion is a wrapper, but that's mostly beside the point. What Micha never considered was the possibility that someone would try to do their own swap in CFML at runtime.
The static vars on the CFML Engine impl are set to
public
and
final
so while you can access them, you probably can't change them at runtime.
a

Adam Cameron

03/23/2022, 9:32 PM
Yeah I figured it was something along those lines.
TBH it's nae bother cos what I was hoping to do just won't work any (because of our code, not because of anything we're discussing here) 😐
👍 1
Fun though. And I learned some stuff
b

bdw429s

03/23/2022, 9:33 PM
Now, on a totally unrelated note-- you can also avoid the entire file writing bit by just using a
ByteArrayOutputStream
instead of a file ouput stream. It will buffer the input internally and you can just grab its contents when your test is done.
a

Adam Cameron

03/23/2022, 9:34 PM
AH
b

bdw429s

03/23/2022, 9:34 PM
you'd use
BAOS.toString( "UTF-8" )
to get the bytes back out as a string
a

Adam Cameron

03/23/2022, 9:34 PM
I as hoping to grab it from the stream direct, but didn't find one I could read back from. Thanks!
b

bdw429s

03/23/2022, 9:34 PM
I love Java's input/output stream/reader classes, but they're a sea of options....
a

Adam Cameron

03/23/2022, 9:45 PM
Well... you know... Java. Needs to be able to do... everything.
👍 1